mirror of https://github.com/jetkvm/kvm.git
Merge pull request #1 from IDisposable/small-tweaks
Some cleanup to eliminate duplicate code ensure we don't carry around multiple copies of state
This commit is contained in:
commit
bc2a5f88e1
203
audio.go
203
audio.go
|
|
@ -15,10 +15,10 @@ var (
|
|||
audioMutex sync.Mutex
|
||||
setAudioTrackMutex sync.Mutex // Prevents concurrent setAudioTrack() calls
|
||||
inputSourceMutex sync.Mutex // Serializes Connect() and WriteMessage() calls to input source
|
||||
outputSource audio.AudioSource
|
||||
outputSource atomic.Pointer[audio.AudioSource]
|
||||
inputSource atomic.Pointer[audio.AudioSource]
|
||||
outputRelay *audio.OutputRelay
|
||||
inputRelay *audio.InputRelay
|
||||
outputRelay atomic.Pointer[audio.OutputRelay]
|
||||
inputRelay atomic.Pointer[audio.InputRelay]
|
||||
audioInitialized bool
|
||||
activeConnections atomic.Int32
|
||||
audioLogger zerolog.Logger
|
||||
|
|
@ -28,6 +28,14 @@ var (
|
|||
audioInputEnabled atomic.Bool
|
||||
)
|
||||
|
||||
func getAlsaDevice(source string) string {
|
||||
if source == "hdmi" {
|
||||
return "hw:0,0"
|
||||
} else {
|
||||
return "hw:1,0"
|
||||
}
|
||||
}
|
||||
|
||||
func initAudio() {
|
||||
audioLogger = logging.GetDefaultLogger().With().Str("component", "audio-manager").Logger()
|
||||
|
||||
|
|
@ -40,7 +48,7 @@ func initAudio() {
|
|||
}
|
||||
|
||||
func getAudioConfig() audio.AudioConfig {
|
||||
ensureConfigLoaded()
|
||||
// config is already loaded
|
||||
cfg := audio.DefaultAudioConfig()
|
||||
if config.AudioBitrate >= 64 && config.AudioBitrate <= 256 {
|
||||
cfg.Bitrate = uint16(config.AudioBitrate)
|
||||
|
|
@ -71,63 +79,81 @@ func startAudio() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
if outputSource == nil && audioOutputEnabled.Load() && currentAudioTrack != nil {
|
||||
ensureConfigLoaded()
|
||||
alsaDevice := "hw:1,0"
|
||||
if config.AudioOutputSource == "hdmi" {
|
||||
alsaDevice = "hw:0,0"
|
||||
}
|
||||
|
||||
source := audio.NewCgoOutputSource(alsaDevice)
|
||||
source.SetConfig(getAudioConfig())
|
||||
outputSource = source
|
||||
outputRelay = audio.NewOutputRelay(outputSource, currentAudioTrack)
|
||||
if err := outputRelay.Start(); err != nil {
|
||||
audioLogger.Error().Err(err).Msg("Failed to start audio output relay")
|
||||
}
|
||||
if activeConnections.Load() <= 0 {
|
||||
audioLogger.Debug().Msg("No active connections, skipping audio start")
|
||||
return nil
|
||||
}
|
||||
|
||||
ensureConfigLoaded()
|
||||
if inputSource.Load() == nil && audioInputEnabled.Load() && config.UsbDevices != nil && config.UsbDevices.Audio {
|
||||
alsaPlaybackDevice := "hw:1,0"
|
||||
|
||||
source := audio.NewCgoInputSource(alsaPlaybackDevice)
|
||||
source.SetConfig(getAudioConfig())
|
||||
var audioSource audio.AudioSource = source
|
||||
inputSource.Store(&audioSource)
|
||||
if audioOutputEnabled.Load() && currentAudioTrack != nil {
|
||||
startOutputAudioUnderMutex(getAlsaDevice(config.AudioOutputSource))
|
||||
}
|
||||
|
||||
inputRelay = audio.NewInputRelay(audioSource)
|
||||
if err := inputRelay.Start(); err != nil {
|
||||
audioLogger.Error().Err(err).Msg("Failed to start input relay")
|
||||
}
|
||||
if audioInputEnabled.Load() && config.UsbDevices != nil && config.UsbDevices.Audio {
|
||||
startInputAudioUnderMutex(getAlsaDevice("usb"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func startOutputAudioUnderMutex(alsaOutputDevice string) {
|
||||
newSource := audio.NewCgoOutputSource(alsaOutputDevice, getAudioConfig())
|
||||
oldSource := outputSource.Swap(&newSource)
|
||||
newRelay := audio.NewOutputRelay(&newSource, currentAudioTrack)
|
||||
oldRelay := outputRelay.Swap(newRelay)
|
||||
|
||||
if oldRelay != nil {
|
||||
oldRelay.Stop()
|
||||
}
|
||||
|
||||
if oldSource != nil {
|
||||
(*oldSource).Disconnect()
|
||||
}
|
||||
|
||||
if err := newRelay.Start(); err != nil {
|
||||
audioLogger.Error().Err(err).Str("alsaOutputDevice", alsaOutputDevice).Msg("Failed to start audio output relay")
|
||||
}
|
||||
}
|
||||
|
||||
func startInputAudioUnderMutex(alsaPlaybackDevice string) {
|
||||
newSource := audio.NewCgoInputSource(alsaPlaybackDevice, getAudioConfig())
|
||||
oldSource := outputSource.Swap(&newSource)
|
||||
newRelay := audio.NewInputRelay(&newSource)
|
||||
oldRelay := inputRelay.Swap(newRelay)
|
||||
|
||||
if oldRelay != nil {
|
||||
oldRelay.Stop()
|
||||
}
|
||||
|
||||
if oldSource != nil {
|
||||
(*oldSource).Disconnect()
|
||||
}
|
||||
|
||||
if err := newRelay.Start(); err != nil {
|
||||
audioLogger.Error().Err(err).Str("alsaPlaybackDevice", alsaPlaybackDevice).Msg("Failed to start input relay")
|
||||
}
|
||||
}
|
||||
|
||||
func stopOutputAudio() {
|
||||
audioMutex.Lock()
|
||||
outRelay := outputRelay
|
||||
outSource := outputSource
|
||||
outputRelay = nil
|
||||
outputSource = nil
|
||||
outRelay := outputRelay.Swap(nil)
|
||||
outSource := outputSource.Swap(nil)
|
||||
audioMutex.Unlock()
|
||||
|
||||
if outRelay != nil {
|
||||
outRelay.Stop()
|
||||
}
|
||||
if outSource != nil {
|
||||
outSource.Disconnect()
|
||||
(*outSource).Disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
func stopInputAudio() {
|
||||
audioMutex.Lock()
|
||||
inRelay := inputRelay
|
||||
inputRelay = nil
|
||||
audioMutex.Unlock()
|
||||
|
||||
inRelay := inputRelay.Swap(nil)
|
||||
inSource := inputSource.Swap(nil)
|
||||
audioMutex.Unlock()
|
||||
|
||||
if inRelay != nil {
|
||||
inRelay.Stop()
|
||||
|
|
@ -153,7 +179,7 @@ func onWebRTCConnect() {
|
|||
|
||||
func onWebRTCDisconnect() {
|
||||
count := activeConnections.Add(-1)
|
||||
if count == 0 {
|
||||
if count <= 0 {
|
||||
// Stop audio immediately to release HDMI audio device which shares hardware with video device
|
||||
stopAudio()
|
||||
}
|
||||
|
|
@ -163,42 +189,12 @@ func setAudioTrack(audioTrack *webrtc.TrackLocalStaticSample) {
|
|||
setAudioTrackMutex.Lock()
|
||||
defer setAudioTrackMutex.Unlock()
|
||||
|
||||
// Capture old resources and update state in single critical section
|
||||
audioMutex.Lock()
|
||||
stopOutputAudio()
|
||||
|
||||
currentAudioTrack = audioTrack
|
||||
oldRelay := outputRelay
|
||||
oldSource := outputSource
|
||||
outputRelay = nil
|
||||
outputSource = nil
|
||||
|
||||
var newRelay *audio.OutputRelay
|
||||
var newSource audio.AudioSource
|
||||
if currentAudioTrack != nil && audioOutputEnabled.Load() {
|
||||
ensureConfigLoaded()
|
||||
alsaDevice := "hw:1,0"
|
||||
if config.AudioOutputSource == "hdmi" {
|
||||
alsaDevice = "hw:0,0"
|
||||
}
|
||||
newSource = audio.NewCgoOutputSource(alsaDevice)
|
||||
newSource.SetConfig(getAudioConfig())
|
||||
newRelay = audio.NewOutputRelay(newSource, currentAudioTrack)
|
||||
outputSource = newSource
|
||||
outputRelay = newRelay
|
||||
}
|
||||
audioMutex.Unlock()
|
||||
|
||||
// Stop/start resources outside mutex to avoid blocking on CGO calls
|
||||
if oldRelay != nil {
|
||||
oldRelay.Stop()
|
||||
}
|
||||
if oldSource != nil {
|
||||
oldSource.Disconnect()
|
||||
}
|
||||
|
||||
if newRelay != nil {
|
||||
if err := newRelay.Start(); err != nil {
|
||||
audioLogger.Error().Err(err).Msg("Failed to start output relay")
|
||||
}
|
||||
if err := startAudio(); err != nil {
|
||||
audioLogger.Error().Err(err).Msg("Failed to start with new audio track")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -250,79 +246,44 @@ func SetAudioOutputSource(source string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
stopOutputAudio()
|
||||
config.AudioOutputSource = source
|
||||
|
||||
stopOutputAudio()
|
||||
|
||||
if audioOutputEnabled.Load() && activeConnections.Load() > 0 && currentAudioTrack != nil {
|
||||
alsaDevice := "hw:1,0"
|
||||
if source == "hdmi" {
|
||||
alsaDevice = "hw:0,0"
|
||||
}
|
||||
|
||||
newSource := audio.NewCgoOutputSource(alsaDevice)
|
||||
newSource.SetConfig(getAudioConfig())
|
||||
newRelay := audio.NewOutputRelay(newSource, currentAudioTrack)
|
||||
|
||||
audioMutex.Lock()
|
||||
outputSource = newSource
|
||||
outputRelay = newRelay
|
||||
audioMutex.Unlock()
|
||||
|
||||
if err := newRelay.Start(); err != nil {
|
||||
audioLogger.Error().Err(err).Str("source", source).Msg("Failed to start audio relay with new source")
|
||||
}
|
||||
if err := startAudio(); err != nil {
|
||||
audioLogger.Error().Err(err).Str("source", source).Msg("Failed to start audio output after source change")
|
||||
}
|
||||
|
||||
return SaveConfig()
|
||||
}
|
||||
|
||||
func RestartAudioOutput() {
|
||||
func RestartAudioOutput() error {
|
||||
audioMutex.Lock()
|
||||
hasActiveOutput := outputSource != nil && currentAudioTrack != nil && audioOutputEnabled.Load()
|
||||
hasActiveOutput := audioOutputEnabled.Load() && currentAudioTrack != nil && outputSource.Load() != nil
|
||||
audioMutex.Unlock()
|
||||
|
||||
if !hasActiveOutput {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
audioLogger.Info().Msg("Restarting audio output")
|
||||
|
||||
stopOutputAudio()
|
||||
|
||||
ensureConfigLoaded()
|
||||
alsaDevice := "hw:1,0"
|
||||
if config.AudioOutputSource == "hdmi" {
|
||||
alsaDevice = "hw:0,0"
|
||||
}
|
||||
|
||||
newSource := audio.NewCgoOutputSource(alsaDevice)
|
||||
newSource.SetConfig(getAudioConfig())
|
||||
newRelay := audio.NewOutputRelay(newSource, currentAudioTrack)
|
||||
|
||||
audioMutex.Lock()
|
||||
outputSource = newSource
|
||||
outputRelay = newRelay
|
||||
audioMutex.Unlock()
|
||||
|
||||
if err := newRelay.Start(); err != nil {
|
||||
audioLogger.Error().Err(err).Msg("Failed to restart audio output")
|
||||
}
|
||||
return startAudio()
|
||||
}
|
||||
|
||||
func handleInputTrackForSession(track *webrtc.TrackRemote) {
|
||||
myTrackID := track.ID()
|
||||
|
||||
audioLogger.Debug().
|
||||
trackLogger := audioLogger.With().
|
||||
Str("codec", track.Codec().MimeType).
|
||||
Str("track_id", myTrackID).
|
||||
Msg("starting input track handler")
|
||||
Logger()
|
||||
|
||||
trackLogger.Debug().Msg("starting input track handler")
|
||||
|
||||
for {
|
||||
currentTrackID := currentInputTrack.Load()
|
||||
if currentTrackID != nil && *currentTrackID != myTrackID {
|
||||
audioLogger.Debug().
|
||||
Str("my_track_id", myTrackID).
|
||||
trackLogger.Debug().
|
||||
Str("current_track_id", *currentTrackID).
|
||||
Msg("input track handler exiting - superseded")
|
||||
return
|
||||
|
|
@ -331,10 +292,10 @@ func handleInputTrackForSession(track *webrtc.TrackRemote) {
|
|||
rtpPacket, _, err := track.ReadRTP()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
audioLogger.Debug().Str("track_id", myTrackID).Msg("input track ended")
|
||||
trackLogger.Debug().Msg("input track ended")
|
||||
return
|
||||
}
|
||||
audioLogger.Warn().Err(err).Str("track_id", myTrackID).Msg("failed to read RTP packet")
|
||||
trackLogger.Warn().Err(err).Msg("failed to read RTP packet")
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,46 +25,47 @@ const (
|
|||
)
|
||||
|
||||
type CgoSource struct {
|
||||
direction string
|
||||
alsaDevice string
|
||||
initialized bool
|
||||
connected bool
|
||||
mu sync.Mutex
|
||||
logger zerolog.Logger
|
||||
opusBuf []byte
|
||||
config AudioConfig
|
||||
outputDevice bool
|
||||
alsaDevice string
|
||||
connected bool
|
||||
mu sync.Mutex
|
||||
logger zerolog.Logger
|
||||
opusBuf []byte
|
||||
config AudioConfig
|
||||
}
|
||||
|
||||
func NewCgoOutputSource(alsaDevice string) *CgoSource {
|
||||
logger := logging.GetDefaultLogger().With().Str("component", "audio-output-cgo").Logger()
|
||||
var _ AudioSource = (*CgoSource)(nil)
|
||||
|
||||
func NewCgoOutputSource(alsaDevice string, cfg AudioConfig) AudioSource {
|
||||
logger := logging.GetDefaultLogger().With().
|
||||
Str("component", "audio-output-cgo").
|
||||
Str("alsa_device", alsaDevice).
|
||||
Logger()
|
||||
|
||||
return &CgoSource{
|
||||
direction: "output",
|
||||
alsaDevice: alsaDevice,
|
||||
logger: logger,
|
||||
opusBuf: make([]byte, ipcMaxFrameSize),
|
||||
config: DefaultAudioConfig(),
|
||||
outputDevice: true,
|
||||
alsaDevice: alsaDevice,
|
||||
logger: logger,
|
||||
opusBuf: make([]byte, ipcMaxFrameSize),
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCgoInputSource(alsaDevice string) *CgoSource {
|
||||
logger := logging.GetDefaultLogger().With().Str("component", "audio-input-cgo").Logger()
|
||||
func NewCgoInputSource(alsaDevice string, cfg AudioConfig) AudioSource {
|
||||
logger := logging.GetDefaultLogger().With().
|
||||
Str("component", "audio-input-cgo").
|
||||
Str("alsa_device", alsaDevice).
|
||||
Logger()
|
||||
|
||||
return &CgoSource{
|
||||
direction: "input",
|
||||
alsaDevice: alsaDevice,
|
||||
logger: logger,
|
||||
opusBuf: make([]byte, ipcMaxFrameSize),
|
||||
config: DefaultAudioConfig(),
|
||||
outputDevice: false,
|
||||
alsaDevice: alsaDevice,
|
||||
logger: logger,
|
||||
opusBuf: make([]byte, ipcMaxFrameSize),
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CgoSource) SetConfig(cfg AudioConfig) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.config = cfg
|
||||
}
|
||||
|
||||
func (c *CgoSource) Connect() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
|
@ -73,7 +74,7 @@ func (c *CgoSource) Connect() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
if c.direction == "output" {
|
||||
if c.outputDevice {
|
||||
os.Setenv("ALSA_CAPTURE_DEVICE", c.alsaDevice)
|
||||
|
||||
dtx := C.uchar(0)
|
||||
|
|
@ -93,7 +94,6 @@ func (c *CgoSource) Connect() error {
|
|||
Uint8("buffer_periods", c.config.BufferPeriods).
|
||||
Uint32("sample_rate", c.config.SampleRate).
|
||||
Uint8("packet_loss_perc", c.config.PacketLossPerc).
|
||||
Str("alsa_device", c.alsaDevice).
|
||||
Msg("Initializing audio capture")
|
||||
|
||||
C.update_audio_constants(
|
||||
|
|
@ -139,7 +139,6 @@ func (c *CgoSource) Connect() error {
|
|||
}
|
||||
|
||||
c.connected = true
|
||||
c.initialized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -151,10 +150,12 @@ func (c *CgoSource) Disconnect() {
|
|||
return
|
||||
}
|
||||
|
||||
if c.direction == "output" {
|
||||
if c.outputDevice {
|
||||
C.jetkvm_audio_capture_close()
|
||||
os.Unsetenv("ALSA_CAPTURE_DEVICE")
|
||||
} else {
|
||||
C.jetkvm_audio_playback_close()
|
||||
os.Unsetenv("ALSA_PLAYBACK_DEVICE")
|
||||
}
|
||||
|
||||
c.connected = false
|
||||
|
|
@ -173,7 +174,7 @@ func (c *CgoSource) ReadMessage() (uint8, []byte, error) {
|
|||
return 0, nil, fmt.Errorf("not connected")
|
||||
}
|
||||
|
||||
if c.direction != "output" {
|
||||
if !c.outputDevice {
|
||||
c.mu.Unlock()
|
||||
return 0, nil, fmt.Errorf("ReadMessage only supported for output direction")
|
||||
}
|
||||
|
|
@ -203,7 +204,7 @@ func (c *CgoSource) WriteMessage(msgType uint8, payload []byte) error {
|
|||
return fmt.Errorf("not connected")
|
||||
}
|
||||
|
||||
if c.direction != "input" {
|
||||
if c.outputDevice {
|
||||
c.mu.Unlock()
|
||||
return fmt.Errorf("WriteMessage only supported for input direction")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ package audio
|
|||
|
||||
type CgoSource struct{}
|
||||
|
||||
func NewCgoOutputSource(alsaDevice string) *CgoSource {
|
||||
var _ AudioSource = (*CgoSource)(nil)
|
||||
|
||||
func NewCgoOutputSource(alsaDevice string, audioConfig AudioConfig) AudioSource {
|
||||
panic("audio CGO source not supported on this platform")
|
||||
}
|
||||
|
||||
func NewCgoInputSource(alsaDevice string) *CgoSource {
|
||||
func NewCgoInputSource(alsaDevice string, audioConfig AudioConfig) AudioSource {
|
||||
panic("audio CGO source not supported on this platform")
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +35,3 @@ func (c *CgoSource) ReadMessage() (uint8, []byte, error) {
|
|||
func (c *CgoSource) WriteMessage(msgType uint8, payload []byte) error {
|
||||
panic("audio CGO source not supported on this platform")
|
||||
}
|
||||
|
||||
func (c *CgoSource) SetConfig(cfg AudioConfig) {
|
||||
panic("audio CGO source not supported on this platform")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
type OutputRelay struct {
|
||||
source AudioSource
|
||||
source *AudioSource
|
||||
audioTrack *webrtc.TrackLocalStaticSample
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
|
@ -26,7 +26,7 @@ type OutputRelay struct {
|
|||
framesDropped atomic.Uint32
|
||||
}
|
||||
|
||||
func NewOutputRelay(source AudioSource, audioTrack *webrtc.TrackLocalStaticSample) *OutputRelay {
|
||||
func NewOutputRelay(source *AudioSource, audioTrack *webrtc.TrackLocalStaticSample) *OutputRelay {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
logger := logging.GetDefaultLogger().With().Str("component", "audio-output-relay").Logger()
|
||||
|
||||
|
|
@ -73,19 +73,19 @@ func (r *OutputRelay) relayLoop() {
|
|||
const reconnectDelay = 1 * time.Second
|
||||
|
||||
for r.running.Load() {
|
||||
if !r.source.IsConnected() {
|
||||
if err := r.source.Connect(); err != nil {
|
||||
if !(*r.source).IsConnected() {
|
||||
if err := (*r.source).Connect(); err != nil {
|
||||
r.logger.Debug().Err(err).Msg("failed to connect, will retry")
|
||||
time.Sleep(reconnectDelay)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
msgType, payload, err := r.source.ReadMessage()
|
||||
msgType, payload, err := (*r.source).ReadMessage()
|
||||
if err != nil {
|
||||
if r.running.Load() {
|
||||
r.logger.Warn().Err(err).Msg("read error, reconnecting")
|
||||
r.source.Disconnect()
|
||||
(*r.source).Disconnect()
|
||||
time.Sleep(reconnectDelay)
|
||||
}
|
||||
continue
|
||||
|
|
@ -104,14 +104,14 @@ func (r *OutputRelay) relayLoop() {
|
|||
}
|
||||
|
||||
type InputRelay struct {
|
||||
source AudioSource
|
||||
source *AudioSource
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
logger zerolog.Logger
|
||||
running atomic.Bool
|
||||
}
|
||||
|
||||
func NewInputRelay(source AudioSource) *InputRelay {
|
||||
func NewInputRelay(source *AudioSource) *InputRelay {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
logger := logging.GetDefaultLogger().With().Str("component", "audio-input-relay").Logger()
|
||||
|
||||
|
|
|
|||
|
|
@ -32,5 +32,4 @@ type AudioSource interface {
|
|||
IsConnected() bool
|
||||
Connect() error
|
||||
Disconnect()
|
||||
SetConfig(cfg AudioConfig)
|
||||
}
|
||||
|
|
|
|||
68
jsonrpc.go
68
jsonrpc.go
|
|
@ -18,7 +18,6 @@ import (
|
|||
"github.com/rs/zerolog"
|
||||
"go.bug.st/serial"
|
||||
|
||||
"github.com/jetkvm/kvm/internal/audio"
|
||||
"github.com/jetkvm/kvm/internal/hidrpc"
|
||||
"github.com/jetkvm/kvm/internal/usbgadget"
|
||||
"github.com/jetkvm/kvm/internal/utils"
|
||||
|
|
@ -688,10 +687,12 @@ func rpcGetUsbConfig() (usbgadget.Config, error) {
|
|||
|
||||
func rpcSetUsbConfig(usbConfig usbgadget.Config) error {
|
||||
LoadConfig()
|
||||
wasUsbAudioEnabled := config.UsbDevices != nil && config.UsbDevices.Audio
|
||||
|
||||
config.UsbConfig = &usbConfig
|
||||
gadget.SetGadgetConfig(config.UsbConfig)
|
||||
wasAudioEnabled := config.UsbDevices != nil && config.UsbDevices.Audio
|
||||
return updateUsbRelatedConfig(wasAudioEnabled)
|
||||
|
||||
return updateUsbRelatedConfig(wasUsbAudioEnabled)
|
||||
}
|
||||
|
||||
func rpcGetWakeOnLanDevices() ([]WakeOnLanDevice, error) {
|
||||
|
|
@ -903,42 +904,23 @@ func rpcGetUsbDevices() (usbgadget.Devices, error) {
|
|||
return *config.UsbDevices, nil
|
||||
}
|
||||
|
||||
func updateUsbRelatedConfig(wasAudioEnabled bool) error {
|
||||
func updateUsbRelatedConfig(wasUsbAudioEnabled bool) error {
|
||||
ensureConfigLoaded()
|
||||
nowHasUsbAudio := config.UsbDevices != nil && config.UsbDevices.Audio
|
||||
outputSourceIsUsb := config.AudioOutputSource == "usb"
|
||||
|
||||
audioMutex.Lock()
|
||||
inRelay := inputRelay
|
||||
inputRelay = nil
|
||||
audioMutex.Unlock()
|
||||
// must stop input audio before reconfiguring
|
||||
stopInputAudio()
|
||||
|
||||
inSource := inputSource.Swap(nil)
|
||||
|
||||
if inRelay != nil {
|
||||
inRelay.Stop()
|
||||
}
|
||||
if inSource != nil {
|
||||
(*inSource).Disconnect()
|
||||
}
|
||||
|
||||
// Auto-switch to HDMI audio output when USB audio is disabled
|
||||
audioNowEnabled := config.UsbDevices != nil && config.UsbDevices.Audio
|
||||
if wasAudioEnabled && !audioNowEnabled && config.AudioOutputSource == "usb" {
|
||||
config.AudioOutputSource = "hdmi"
|
||||
// if we're currently sourcing audio from USB, stop the output audio before reconfiguring
|
||||
if outputSourceIsUsb {
|
||||
stopOutputAudio()
|
||||
if audioOutputEnabled.Load() && activeConnections.Load() > 0 && currentAudioTrack != nil {
|
||||
newSource := audio.NewCgoOutputSource("hw:0,0")
|
||||
newSource.SetConfig(getAudioConfig())
|
||||
newRelay := audio.NewOutputRelay(newSource, currentAudioTrack)
|
||||
}
|
||||
|
||||
audioMutex.Lock()
|
||||
outputSource = newSource
|
||||
outputRelay = newRelay
|
||||
audioMutex.Unlock()
|
||||
|
||||
if err := newRelay.Start(); err != nil {
|
||||
logger.Warn().Err(err).Msg("Failed to start HDMI audio after USB audio disabled")
|
||||
}
|
||||
}
|
||||
// Auto-switch to HDMI audio output when USB audio was selected and is now disabled
|
||||
if wasUsbAudioEnabled && !nowHasUsbAudio && config.AudioOutputSource == "usb" {
|
||||
logger.Info().Msg("USB audio just disabled, automatic switch audio output source to HDMI")
|
||||
config.AudioOutputSource = "hdmi"
|
||||
}
|
||||
|
||||
if err := gadget.UpdateGadgetConfig(); err != nil {
|
||||
|
|
@ -949,18 +931,15 @@ func updateUsbRelatedConfig(wasAudioEnabled bool) error {
|
|||
return fmt.Errorf("failed to save config: %w", err)
|
||||
}
|
||||
|
||||
// Restart audio if USB audio is enabled with active connections
|
||||
if activeConnections.Load() > 0 && config.UsbDevices != nil && config.UsbDevices.Audio {
|
||||
if err := startAudio(); err != nil {
|
||||
logger.Warn().Err(err).Msg("Failed to restart audio after USB reconfiguration")
|
||||
}
|
||||
if err := startAudio(); err != nil {
|
||||
logger.Warn().Err(err).Msg("Failed to restart audio after USB reconfiguration")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
|
||||
wasAudioEnabled := config.UsbDevices != nil && config.UsbDevices.Audio
|
||||
wasUsbAudioEnabled := config.UsbDevices != nil && config.UsbDevices.Audio
|
||||
currentDevices := gadget.GetGadgetDevices()
|
||||
|
||||
// Skip reconfiguration if devices haven't changed to avoid HID disruption
|
||||
|
|
@ -972,11 +951,11 @@ func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
|
|||
config.UsbDevices = &usbDevices
|
||||
gadget.SetGadgetDevices(config.UsbDevices)
|
||||
|
||||
return updateUsbRelatedConfig(wasAudioEnabled)
|
||||
return updateUsbRelatedConfig(wasUsbAudioEnabled)
|
||||
}
|
||||
|
||||
func rpcSetUsbDeviceState(device string, enabled bool) error {
|
||||
wasAudioEnabled := config.UsbDevices != nil && config.UsbDevices.Audio
|
||||
wasUsbAudioEnabled := config.UsbDevices != nil && config.UsbDevices.Audio
|
||||
currentDevices := gadget.GetGadgetDevices()
|
||||
|
||||
switch device {
|
||||
|
|
@ -1001,7 +980,7 @@ func rpcSetUsbDeviceState(device string, enabled bool) error {
|
|||
}
|
||||
|
||||
gadget.SetGadgetDevices(config.UsbDevices)
|
||||
return updateUsbRelatedConfig(wasAudioEnabled)
|
||||
return updateUsbRelatedConfig(wasUsbAudioEnabled)
|
||||
}
|
||||
|
||||
func rpcGetAudioOutputEnabled() (bool, error) {
|
||||
|
|
@ -1104,8 +1083,7 @@ func rpcSetAudioConfig(bitrate int, complexity int, dtxEnabled bool, fecEnabled
|
|||
}
|
||||
|
||||
func rpcRestartAudioOutput() error {
|
||||
RestartAudioOutput()
|
||||
return nil
|
||||
return RestartAudioOutput()
|
||||
}
|
||||
|
||||
func rpcGetAudioInputAutoEnable() (bool, error) {
|
||||
|
|
|
|||
4
main.go
4
main.go
|
|
@ -42,7 +42,9 @@ func Main() {
|
|||
|
||||
initDisplay()
|
||||
initNative(systemVersionLocal, appVersionLocal)
|
||||
|
||||
initAudio()
|
||||
defer stopAudio()
|
||||
|
||||
http.DefaultClient.Timeout = 1 * time.Minute
|
||||
|
||||
|
|
@ -81,6 +83,7 @@ func Main() {
|
|||
if err := initImagesFolder(); err != nil {
|
||||
logger.Warn().Err(err).Msg("failed to init images folder")
|
||||
}
|
||||
|
||||
initJiggler()
|
||||
|
||||
// start video sleep mode timer
|
||||
|
|
@ -140,7 +143,6 @@ func Main() {
|
|||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigs
|
||||
|
||||
stopAudio()
|
||||
logger.Log().Msg("JetKVM Shutting Down")
|
||||
|
||||
//if fuseServer != nil {
|
||||
|
|
|
|||
|
|
@ -49,8 +49,6 @@
|
|||
"access_update_tls_settings": "Opdater TLS-indstillinger",
|
||||
"action_bar_audio": "Lyd",
|
||||
"action_bar_connection_stats": "Forbindelsesstatistik",
|
||||
"audio_input_disabled": "Lydindgang deaktiveret",
|
||||
"audio_input_enabled": "Lydindgang aktiveret",
|
||||
"audio_input_failed_disable": "Kunne ikke deaktivere lydindgang: {error}",
|
||||
"audio_input_failed_enable": "Kunne ikke aktivere lydindgang: {error}",
|
||||
"audio_input_auto_enable_disabled": "Automatisk aktivering af mikrofon deaktiveret",
|
||||
|
|
@ -167,6 +165,46 @@
|
|||
"atx_power_control_reset_button": "Nulstil",
|
||||
"atx_power_control_send_action_error": "Kunne ikke sende ATX-strømfunktion {action} : {error}",
|
||||
"atx_power_control_short_power_button": "Kort tryk",
|
||||
"audio_https_only": "Kun HTTPS",
|
||||
"audio_input_auto_enable_disabled": "Automatisk aktivering af mikrofon deaktiveret",
|
||||
"audio_input_auto_enable_enabled": "Automatisk aktivering af mikrofon aktiveret",
|
||||
"audio_input_failed_disable": "Kunne ikke deaktivere lydindgang: {error}",
|
||||
"audio_input_failed_enable": "Kunne ikke aktivere lydindgang: {error}",
|
||||
"audio_microphone_description": "Mikrofonindgang til mål",
|
||||
"audio_microphone_title": "Mikrofon",
|
||||
"audio_output_disabled": "Lydudgang deaktiveret",
|
||||
"audio_output_enabled": "Lydudgang aktiveret",
|
||||
"audio_output_failed_disable": "Kunne ikke deaktivere lydudgang: {error}",
|
||||
"audio_output_failed_enable": "Kunne ikke aktivere lydudgang: {error}",
|
||||
"audio_popover_description": "Hurtige lydkontroller til højttalere og mikrofon",
|
||||
"audio_popover_title": "Lyd",
|
||||
"audio_settings_applied": "Lydindstillinger anvendt",
|
||||
"audio_settings_apply_button": "Anvend indstillinger",
|
||||
"audio_settings_auto_enable_microphone_description": "Aktiver automatisk browsermikrofon ved tilslutning (ellers skal du aktivere det manuelt ved hver session)",
|
||||
"audio_settings_auto_enable_microphone_title": "Aktiver mikrofon automatisk",
|
||||
"audio_settings_bitrate_description": "Lydkodningsbitrate (højere = bedre kvalitet, mere båndbredde)",
|
||||
"audio_settings_bitrate_title": "Opus Bitrate",
|
||||
"audio_settings_buffer_description": "ALSA bufferstørrelse (højere = mere stabil, mere latens)",
|
||||
"audio_settings_buffer_title": "Bufferperioder",
|
||||
"audio_settings_complexity_description": "Encoder-kompleksitet (0-10, højere = bedre kvalitet, mere CPU)",
|
||||
"audio_settings_complexity_title": "Opus Kompleksitet",
|
||||
"audio_settings_config_updated": "Lydkonfiguration opdateret",
|
||||
"audio_settings_description": "Konfigurer lydindgangs- og lydudgangsindstillinger for din JetKVM-enhed",
|
||||
"audio_settings_dtx_description": "Spar båndbredde under stilhed",
|
||||
"audio_settings_dtx_title": "DTX (Diskontinuerlig Transmission)",
|
||||
"audio_settings_fec_description": "Forbedre lydkvaliteten på tabende forbindelser",
|
||||
"audio_settings_fec_title": "FEC (Fremadrettet Fejlkorrektion)",
|
||||
"audio_settings_hdmi_label": "HDMI",
|
||||
"audio_settings_output_description": "Aktiver eller deaktiver lyd fra fjerncomputeren",
|
||||
"audio_settings_output_source_description": "Vælg lydoptagelsesenheden (HDMI eller USB)",
|
||||
"audio_settings_output_source_failed": "Kunne ikke indstille lydudgangskilde: {error}",
|
||||
"audio_settings_output_source_success": "Lydudgangskilde opdateret med succes",
|
||||
"audio_settings_output_source_title": "Lydudgangskilde",
|
||||
"audio_settings_output_title": "Lydudgang",
|
||||
"audio_settings_title": "Lyd",
|
||||
"audio_settings_usb_label": "USB",
|
||||
"audio_speakers_description": "Lyd fra mål til højttalere",
|
||||
"audio_speakers_title": "Højttalere",
|
||||
"auth_authentication_mode": "Vælg venligst en godkendelsestilstand",
|
||||
"auth_authentication_mode_error": "Der opstod en fejl under indstilling af godkendelsestilstanden",
|
||||
"auth_authentication_mode_invalid": "Ugyldig godkendelsestilstand",
|
||||
|
|
@ -223,6 +261,9 @@
|
|||
"connection_stats_packets_lost_description": "Antal mistede indgående video-RTP-pakker.",
|
||||
"connection_stats_playback_delay": "Afspilningsforsinkelse",
|
||||
"connection_stats_playback_delay_description": "Forsinkelse tilføjet af jitterbufferen for at jævne afspilningen, når billeder ankommer ujævnt.",
|
||||
"connection_stats_remote_ip_address": "Fjern IP-adresse",
|
||||
"connection_stats_remote_ip_address_copy_error": "Kunne ikke kopiere fjern-IP-adresse",
|
||||
"connection_stats_remote_ip_address_copy_success": "Fjern IP-adresse { ip } kopieret til udklipsholder",
|
||||
"connection_stats_round_trip_time": "Rundturstid",
|
||||
"connection_stats_round_trip_time_description": "Rundrejsetid for det aktive ICE-kandidatpar mellem peers.",
|
||||
"connection_stats_sidebar": "Forbindelsesstatistik",
|
||||
|
|
@ -748,6 +789,9 @@
|
|||
"peer_connection_failed": "Forbindelsen mislykkedes",
|
||||
"peer_connection_new": "Forbinder",
|
||||
"previous": "Tidligere",
|
||||
"public_ip_card_header": "Offentlige IP-adresser",
|
||||
"public_ip_card_refresh": "Opfriske",
|
||||
"public_ip_card_refresh_error": "Kunne ikke opdatere offentlige IP-adresser: {error}",
|
||||
"register_device_error": "Der opstod en fejl {error} under registrering af din enhed.",
|
||||
"register_device_finish_button": "Afslut opsætning",
|
||||
"register_device_name_description": "Navngiv din enhed, så du nemt kan identificere den senere. Du kan til enhver tid ændre dette navn.",
|
||||
|
|
|
|||
|
|
@ -49,8 +49,6 @@
|
|||
"access_update_tls_settings": "TLS-Einstellungen aktualisieren",
|
||||
"action_bar_audio": "Audio",
|
||||
"action_bar_connection_stats": "Verbindungsstatistiken",
|
||||
"audio_input_disabled": "Audioeingang deaktiviert",
|
||||
"audio_input_enabled": "Audioeingang aktiviert",
|
||||
"audio_input_failed_disable": "Fehler beim Deaktivieren des Audioeingangs: {error}",
|
||||
"audio_input_failed_enable": "Fehler beim Aktivieren des Audioeingangs: {error}",
|
||||
"audio_input_auto_enable_disabled": "Automatische Mikrofonaktivierung deaktiviert",
|
||||
|
|
@ -167,6 +165,46 @@
|
|||
"atx_power_control_reset_button": "Reset-Taste",
|
||||
"atx_power_control_send_action_error": "ATX-Stromversorgungsaktion {action} konnte nicht gesendet werden: {error}",
|
||||
"atx_power_control_short_power_button": "Kurzes Drücken",
|
||||
"audio_https_only": "Nur HTTPS",
|
||||
"audio_input_auto_enable_disabled": "Automatische Mikrofonaktivierung deaktiviert",
|
||||
"audio_input_auto_enable_enabled": "Automatische Mikrofonaktivierung aktiviert",
|
||||
"audio_input_failed_disable": "Fehler beim Deaktivieren des Audioeingangs: {error}",
|
||||
"audio_input_failed_enable": "Fehler beim Aktivieren des Audioeingangs: {error}",
|
||||
"audio_microphone_description": "Mikrofoneingang zum Ziel",
|
||||
"audio_microphone_title": "Mikrofon",
|
||||
"audio_output_disabled": "Audioausgang deaktiviert",
|
||||
"audio_output_enabled": "Audioausgang aktiviert",
|
||||
"audio_output_failed_disable": "Fehler beim Deaktivieren des Audioausgangs: {error}",
|
||||
"audio_output_failed_enable": "Fehler beim Aktivieren des Audioausgangs: {error}",
|
||||
"audio_popover_description": "Schnelle Audiosteuerung für Lautsprecher und Mikrofon",
|
||||
"audio_popover_title": "Audio",
|
||||
"audio_settings_applied": "Audioeinstellungen angewendet",
|
||||
"audio_settings_apply_button": "Einstellungen anwenden",
|
||||
"audio_settings_auto_enable_microphone_description": "Browser-Mikrofon beim Verbinden automatisch aktivieren (andernfalls müssen Sie es in jeder Sitzung manuell aktivieren)",
|
||||
"audio_settings_auto_enable_microphone_title": "Mikrofon automatisch aktivieren",
|
||||
"audio_settings_bitrate_description": "Audio-Codierungsbitrate (höher = bessere Qualität, mehr Bandbreite)",
|
||||
"audio_settings_bitrate_title": "Opus Bitrate",
|
||||
"audio_settings_buffer_description": "ALSA-Puffergröße (höher = stabiler, mehr Latenz)",
|
||||
"audio_settings_buffer_title": "Pufferperioden",
|
||||
"audio_settings_complexity_description": "Encoder-Komplexität (0-10, höher = bessere Qualität, mehr CPU)",
|
||||
"audio_settings_complexity_title": "Opus Komplexität",
|
||||
"audio_settings_config_updated": "Audiokonfiguration aktualisiert",
|
||||
"audio_settings_description": "Konfigurieren Sie Audio-Eingangs- und Ausgangseinstellungen für Ihr JetKVM-Gerät",
|
||||
"audio_settings_dtx_description": "Bandbreite während Stille sparen",
|
||||
"audio_settings_dtx_title": "DTX (Discontinuous Transmission)",
|
||||
"audio_settings_fec_description": "Audioqualität bei verlustbehafteten Verbindungen verbessern",
|
||||
"audio_settings_fec_title": "FEC (Forward Error Correction)",
|
||||
"audio_settings_hdmi_label": "HDMI",
|
||||
"audio_settings_output_description": "Audio vom entfernten Computer aktivieren oder deaktivieren",
|
||||
"audio_settings_output_source_description": "Wählen Sie das Audioaufnahmegerät (HDMI oder USB)",
|
||||
"audio_settings_output_source_failed": "Fehler beim Festlegen der Audioausgabequelle: {error}",
|
||||
"audio_settings_output_source_success": "Audioausgabequelle erfolgreich aktualisiert",
|
||||
"audio_settings_output_source_title": "Audioausgabequelle",
|
||||
"audio_settings_output_title": "Audioausgang",
|
||||
"audio_settings_title": "Audio",
|
||||
"audio_settings_usb_label": "USB",
|
||||
"audio_speakers_description": "Audio vom Ziel zu Lautsprechern",
|
||||
"audio_speakers_title": "Lautsprecher",
|
||||
"auth_authentication_mode": "Bitte wählen Sie einen Authentifizierungsmodus",
|
||||
"auth_authentication_mode_error": "Beim Einstellen des Authentifizierungsmodus ist ein Fehler aufgetreten",
|
||||
"auth_authentication_mode_invalid": "Ungültiger Authentifizierungsmodus",
|
||||
|
|
@ -223,6 +261,9 @@
|
|||
"connection_stats_packets_lost_description": "Anzahl der verlorenen eingehenden Video-RTP-Pakete.",
|
||||
"connection_stats_playback_delay": "Wiedergabeverzögerung",
|
||||
"connection_stats_playback_delay_description": "Durch den Jitter-Puffer hinzugefügte Verzögerung, um die Wiedergabe zu glätten, wenn die Frames ungleichmäßig ankommen.",
|
||||
"connection_stats_remote_ip_address": "Remote-IP-Adresse",
|
||||
"connection_stats_remote_ip_address_copy_error": "Fehler beim Kopieren der Remote-IP-Adresse",
|
||||
"connection_stats_remote_ip_address_copy_success": "Remote-IP-Adresse { ip } in die Zwischenablage kopiert",
|
||||
"connection_stats_round_trip_time": "Round-Trip-Zeit",
|
||||
"connection_stats_round_trip_time_description": "Roundtrip-Zeit für das aktive ICE-Kandidatenpaar zwischen Peers.",
|
||||
"connection_stats_sidebar": "Verbindungsstatistiken",
|
||||
|
|
@ -748,6 +789,9 @@
|
|||
"peer_connection_failed": "Verbindung fehlgeschlagen",
|
||||
"peer_connection_new": "Verbinden",
|
||||
"previous": "Vorherige",
|
||||
"public_ip_card_header": "Öffentliche IP-Adressen",
|
||||
"public_ip_card_refresh": "Aktualisieren",
|
||||
"public_ip_card_refresh_error": "Aktualisierung der öffentlichen IP-Adressen fehlgeschlagen: {error}",
|
||||
"register_device_error": "Beim Registrieren Ihres Geräts ist ein Fehler {error} aufgetreten.",
|
||||
"register_device_finish_button": "Einrichtung abschließen",
|
||||
"register_device_name_description": "Geben Sie Ihrem Gerät einen Namen, damit Sie es später leicht identifizieren können. Sie können diesen Namen jederzeit ändern.",
|
||||
|
|
|
|||
|
|
@ -49,8 +49,6 @@
|
|||
"access_update_tls_settings": "Update TLS Settings",
|
||||
"action_bar_audio": "Audio",
|
||||
"action_bar_connection_stats": "Connection Stats",
|
||||
"audio_input_disabled": "Audio input disabled",
|
||||
"audio_input_enabled": "Audio input enabled",
|
||||
"audio_input_failed_disable": "Failed to disable audio input: {error}",
|
||||
"audio_input_failed_enable": "Failed to enable audio input: {error}",
|
||||
"audio_input_auto_enable_disabled": "Auto-enable microphone disabled",
|
||||
|
|
@ -167,6 +165,46 @@
|
|||
"atx_power_control_reset_button": "Reset",
|
||||
"atx_power_control_send_action_error": "Failed to send ATX power action {action}: {error}",
|
||||
"atx_power_control_short_power_button": "Short Press",
|
||||
"audio_https_only": "HTTPS only",
|
||||
"audio_input_auto_enable_disabled": "Auto-enable microphone disabled",
|
||||
"audio_input_auto_enable_enabled": "Auto-enable microphone enabled",
|
||||
"audio_input_failed_disable": "Failed to disable audio input: {error}",
|
||||
"audio_input_failed_enable": "Failed to enable audio input: {error}",
|
||||
"audio_microphone_description": "Microphone input to target",
|
||||
"audio_microphone_title": "Microphone",
|
||||
"audio_output_disabled": "Audio output disabled",
|
||||
"audio_output_enabled": "Audio output enabled",
|
||||
"audio_output_failed_disable": "Failed to disable audio output: {error}",
|
||||
"audio_output_failed_enable": "Failed to enable audio output: {error}",
|
||||
"audio_popover_description": "Quick audio controls for speakers and microphone",
|
||||
"audio_popover_title": "Audio",
|
||||
"audio_settings_applied": "Audio settings applied",
|
||||
"audio_settings_apply_button": "Apply Settings",
|
||||
"audio_settings_auto_enable_microphone_description": "Automatically enable browser microphone when connecting (otherwise you must manually enable each session)",
|
||||
"audio_settings_auto_enable_microphone_title": "Auto-enable Microphone",
|
||||
"audio_settings_bitrate_description": "Audio encoding bitrate (higher = better quality, more bandwidth)",
|
||||
"audio_settings_bitrate_title": "Opus Bitrate",
|
||||
"audio_settings_buffer_description": "ALSA buffer size (higher = more stable, more latency)",
|
||||
"audio_settings_buffer_title": "Buffer Periods",
|
||||
"audio_settings_complexity_description": "Encoder complexity (0-10, higher = better quality, more CPU)",
|
||||
"audio_settings_complexity_title": "Opus Complexity",
|
||||
"audio_settings_config_updated": "Audio configuration updated",
|
||||
"audio_settings_description": "Configure audio input and output settings for your JetKVM device",
|
||||
"audio_settings_dtx_description": "Save bandwidth during silence",
|
||||
"audio_settings_dtx_title": "DTX (Discontinuous Transmission)",
|
||||
"audio_settings_fec_description": "Improve audio quality on lossy connections",
|
||||
"audio_settings_fec_title": "FEC (Forward Error Correction)",
|
||||
"audio_settings_hdmi_label": "HDMI",
|
||||
"audio_settings_output_description": "Enable or disable audio from the remote computer",
|
||||
"audio_settings_output_source_description": "Select the audio capture device (HDMI or USB)",
|
||||
"audio_settings_output_source_failed": "Failed to set audio output source: {error}",
|
||||
"audio_settings_output_source_success": "Audio output source updated successfully",
|
||||
"audio_settings_output_source_title": "Audio Output Source",
|
||||
"audio_settings_output_title": "Audio Output",
|
||||
"audio_settings_title": "Audio",
|
||||
"audio_settings_usb_label": "USB",
|
||||
"audio_speakers_description": "Audio from target to speakers",
|
||||
"audio_speakers_title": "Speakers",
|
||||
"auth_authentication_mode": "Please select an authentication mode",
|
||||
"auth_authentication_mode_error": "An error occurred while setting the authentication mode",
|
||||
"auth_authentication_mode_invalid": "Invalid authentication mode",
|
||||
|
|
@ -223,6 +261,9 @@
|
|||
"connection_stats_packets_lost_description": "Count of lost inbound video RTP packets.",
|
||||
"connection_stats_playback_delay": "Playback Delay",
|
||||
"connection_stats_playback_delay_description": "Delay added by the jitter buffer to smooth playback when frames arrive unevenly.",
|
||||
"connection_stats_remote_ip_address": "Remote IP Address",
|
||||
"connection_stats_remote_ip_address_copy_error": "Failed to copy remote IP address",
|
||||
"connection_stats_remote_ip_address_copy_success": "Remote IP address { ip } copied to clipboard",
|
||||
"connection_stats_round_trip_time": "Round-Trip Time",
|
||||
"connection_stats_round_trip_time_description": "Round-trip time for the active ICE candidate pair between peers.",
|
||||
"connection_stats_sidebar": "Connection Stats",
|
||||
|
|
@ -748,6 +789,9 @@
|
|||
"peer_connection_failed": "Connection failed",
|
||||
"peer_connection_new": "Connecting",
|
||||
"previous": "Previous",
|
||||
"public_ip_card_header": "Public IP addresses",
|
||||
"public_ip_card_refresh": "Refresh",
|
||||
"public_ip_card_refresh_error": "Failed to refresh public IP addresses: {error}",
|
||||
"register_device_error": "There was an error {error} registering your device.",
|
||||
"register_device_finish_button": "Finish Setup",
|
||||
"register_device_name_description": "Name your device so you can easily identify it later. You can change this name at any time.",
|
||||
|
|
@ -947,11 +991,5 @@
|
|||
"wake_on_lan_invalid_mac": "Invalid MAC address",
|
||||
"wake_on_lan_magic_sent_success": "Magic Packet sent successfully",
|
||||
"welcome_to_jetkvm": "Welcome to JetKVM",
|
||||
"welcome_to_jetkvm_description": "Control any computer remotely","connection_stats_remote_ip_address": "Remote IP Address",
|
||||
"connection_stats_remote_ip_address_description": "The IP address of the remote device.",
|
||||
"connection_stats_remote_ip_address_copy_error": "Failed to copy remote IP address",
|
||||
"connection_stats_remote_ip_address_copy_success": "Remote IP address { ip } copied to clipboard",
|
||||
"public_ip_card_header": "Public IP addresses",
|
||||
"public_ip_card_refresh": "Refresh",
|
||||
"public_ip_card_refresh_error": "Failed to refresh public IP addresses: {error}"
|
||||
"welcome_to_jetkvm_description": "Control any computer remotely"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,8 +49,6 @@
|
|||
"access_update_tls_settings": "Actualizar la configuración de TLS",
|
||||
"action_bar_audio": "Audio",
|
||||
"action_bar_connection_stats": "Estadísticas de conexión",
|
||||
"audio_input_disabled": "Entrada de audio desactivada",
|
||||
"audio_input_enabled": "Entrada de audio activada",
|
||||
"audio_input_failed_disable": "Error al desactivar la entrada de audio: {error}",
|
||||
"audio_input_failed_enable": "Error al activar la entrada de audio: {error}",
|
||||
"audio_input_auto_enable_disabled": "Habilitación automática de micrófono desactivada",
|
||||
|
|
@ -167,6 +165,46 @@
|
|||
"atx_power_control_reset_button": "Reiniciar",
|
||||
"atx_power_control_send_action_error": "No se pudo enviar la acción de alimentación ATX {action} : {error}",
|
||||
"atx_power_control_short_power_button": "Prensa corta",
|
||||
"audio_https_only": "Solo HTTPS",
|
||||
"audio_input_auto_enable_disabled": "Habilitación automática de micrófono desactivada",
|
||||
"audio_input_auto_enable_enabled": "Habilitación automática de micrófono activada",
|
||||
"audio_input_failed_disable": "Error al desactivar la entrada de audio: {error}",
|
||||
"audio_input_failed_enable": "Error al activar la entrada de audio: {error}",
|
||||
"audio_microphone_description": "Entrada de micrófono al objetivo",
|
||||
"audio_microphone_title": "Micrófono",
|
||||
"audio_output_disabled": "Salida de audio desactivada",
|
||||
"audio_output_enabled": "Salida de audio activada",
|
||||
"audio_output_failed_disable": "Error al desactivar la salida de audio: {error}",
|
||||
"audio_output_failed_enable": "Error al activar la salida de audio: {error}",
|
||||
"audio_popover_description": "Controles de audio rápidos para altavoces y micrófono",
|
||||
"audio_popover_title": "Audio",
|
||||
"audio_settings_applied": "Configuración de audio aplicada",
|
||||
"audio_settings_apply_button": "Aplicar configuración",
|
||||
"audio_settings_auto_enable_microphone_description": "Habilitar automáticamente el micrófono del navegador al conectar (de lo contrario, debe habilitarlo manualmente en cada sesión)",
|
||||
"audio_settings_auto_enable_microphone_title": "Habilitar micrófono automáticamente",
|
||||
"audio_settings_bitrate_description": "Tasa de bits de codificación de audio (mayor = mejor calidad, más ancho de banda)",
|
||||
"audio_settings_bitrate_title": "Bitrate Opus",
|
||||
"audio_settings_buffer_description": "Tamaño del buffer ALSA (mayor = más estable, más latencia)",
|
||||
"audio_settings_buffer_title": "Períodos de Buffer",
|
||||
"audio_settings_complexity_description": "Complejidad del codificador (0-10, mayor = mejor calidad, más CPU)",
|
||||
"audio_settings_complexity_title": "Complejidad Opus",
|
||||
"audio_settings_config_updated": "Configuración de audio actualizada",
|
||||
"audio_settings_description": "Configure los ajustes de entrada y salida de audio para su dispositivo JetKVM",
|
||||
"audio_settings_dtx_description": "Ahorrar ancho de banda durante el silencio",
|
||||
"audio_settings_dtx_title": "DTX (Transmisión Discontinua)",
|
||||
"audio_settings_fec_description": "Mejorar la calidad de audio en conexiones con pérdida",
|
||||
"audio_settings_fec_title": "FEC (Corrección de Errores)",
|
||||
"audio_settings_hdmi_label": "HDMI",
|
||||
"audio_settings_output_description": "Habilitar o deshabilitar el audio de la computadora remota",
|
||||
"audio_settings_output_source_description": "Seleccione el dispositivo de captura de audio (HDMI o USB)",
|
||||
"audio_settings_output_source_failed": "Error al configurar la fuente de salida de audio: {error}",
|
||||
"audio_settings_output_source_success": "Fuente de salida de audio actualizada correctamente",
|
||||
"audio_settings_output_source_title": "Fuente de salida de audio",
|
||||
"audio_settings_output_title": "Salida de audio",
|
||||
"audio_settings_title": "Audio",
|
||||
"audio_settings_usb_label": "USB",
|
||||
"audio_speakers_description": "Audio del objetivo a los altavoces",
|
||||
"audio_speakers_title": "Altavoces",
|
||||
"auth_authentication_mode": "Por favor seleccione un modo de autenticación",
|
||||
"auth_authentication_mode_error": "Se produjo un error al configurar el modo de autenticación",
|
||||
"auth_authentication_mode_invalid": "Modo de autenticación no válido",
|
||||
|
|
@ -223,6 +261,9 @@
|
|||
"connection_stats_packets_lost_description": "Recuento de paquetes de vídeo RTP entrantes perdidos.",
|
||||
"connection_stats_playback_delay": "Retraso de reproducción",
|
||||
"connection_stats_playback_delay_description": "Retraso agregado por el buffer de fluctuación para suavizar la reproducción cuando los cuadros llegan de manera desigual.",
|
||||
"connection_stats_remote_ip_address": "Dirección IP remota",
|
||||
"connection_stats_remote_ip_address_copy_error": "No se pudo copiar la dirección IP remota",
|
||||
"connection_stats_remote_ip_address_copy_success": "Dirección IP remota { ip } copiada al portapapeles",
|
||||
"connection_stats_round_trip_time": "Tiempo de ida y vuelta",
|
||||
"connection_stats_round_trip_time_description": "Tiempo de ida y vuelta para el par de candidatos ICE activos entre pares.",
|
||||
"connection_stats_sidebar": "Estadísticas de conexión",
|
||||
|
|
@ -748,6 +789,9 @@
|
|||
"peer_connection_failed": "La conexión falló",
|
||||
"peer_connection_new": "Conectando",
|
||||
"previous": "Anterior",
|
||||
"public_ip_card_header": "Direcciones IP públicas",
|
||||
"public_ip_card_refresh": "Refrescar",
|
||||
"public_ip_card_refresh_error": "Error al actualizar las direcciones IP públicas: {error}",
|
||||
"register_device_error": "Se produjo un error {error} al registrar su dispositivo.",
|
||||
"register_device_finish_button": "Finalizar configuración",
|
||||
"register_device_name_description": "Ponle un nombre a tu dispositivo para que puedas identificarlo fácilmente más tarde. Puedes cambiarlo en cualquier momento.",
|
||||
|
|
|
|||
|
|
@ -49,8 +49,6 @@
|
|||
"access_update_tls_settings": "Mettre à jour les paramètres TLS",
|
||||
"action_bar_audio": "Audio",
|
||||
"action_bar_connection_stats": "Statistiques de connexion",
|
||||
"audio_input_disabled": "Entrée audio désactivée",
|
||||
"audio_input_enabled": "Entrée audio activée",
|
||||
"audio_input_failed_disable": "Échec de la désactivation de l'entrée audio : {error}",
|
||||
"audio_input_failed_enable": "Échec de l'activation de l'entrée audio : {error}",
|
||||
"audio_input_auto_enable_disabled": "Activation automatique du microphone désactivée",
|
||||
|
|
@ -167,6 +165,46 @@
|
|||
"atx_power_control_reset_button": "Réinitialiser",
|
||||
"atx_power_control_send_action_error": "Échec de l'envoi de l'action d'alimentation ATX {action} : {error}",
|
||||
"atx_power_control_short_power_button": "Appui court",
|
||||
"audio_https_only": "HTTPS uniquement",
|
||||
"audio_input_auto_enable_disabled": "Activation automatique du microphone désactivée",
|
||||
"audio_input_auto_enable_enabled": "Activation automatique du microphone activée",
|
||||
"audio_input_failed_disable": "Échec de la désactivation de l'entrée audio : {error}",
|
||||
"audio_input_failed_enable": "Échec de l'activation de l'entrée audio : {error}",
|
||||
"audio_microphone_description": "Entrée microphone vers la cible",
|
||||
"audio_microphone_title": "Microphone",
|
||||
"audio_output_disabled": "Sortie audio désactivée",
|
||||
"audio_output_enabled": "Sortie audio activée",
|
||||
"audio_output_failed_disable": "Échec de la désactivation de la sortie audio : {error}",
|
||||
"audio_output_failed_enable": "Échec de l'activation de la sortie audio : {error}",
|
||||
"audio_popover_description": "Contrôles audio rapides pour haut-parleurs et microphone",
|
||||
"audio_popover_title": "Audio",
|
||||
"audio_settings_applied": "Paramètres audio appliqués",
|
||||
"audio_settings_apply_button": "Appliquer les paramètres",
|
||||
"audio_settings_auto_enable_microphone_description": "Activer automatiquement le microphone du navigateur lors de la connexion (sinon vous devez l'activer manuellement à chaque session)",
|
||||
"audio_settings_auto_enable_microphone_title": "Activer automatiquement le microphone",
|
||||
"audio_settings_bitrate_description": "Débit d'encodage audio (plus élevé = meilleure qualité, plus de bande passante)",
|
||||
"audio_settings_bitrate_title": "Débit Opus",
|
||||
"audio_settings_buffer_description": "Taille du tampon ALSA (plus élevé = plus stable, plus de latence)",
|
||||
"audio_settings_buffer_title": "Périodes de Tampon",
|
||||
"audio_settings_complexity_description": "Complexité de l'encodeur (0-10, plus élevé = meilleure qualité, plus de CPU)",
|
||||
"audio_settings_complexity_title": "Complexité Opus",
|
||||
"audio_settings_config_updated": "Configuration audio mise à jour",
|
||||
"audio_settings_description": "Configurez les paramètres d'entrée et de sortie audio pour votre appareil JetKVM",
|
||||
"audio_settings_dtx_description": "Économiser la bande passante pendant le silence",
|
||||
"audio_settings_dtx_title": "DTX (Transmission Discontinue)",
|
||||
"audio_settings_fec_description": "Améliorer la qualité audio sur les connexions avec perte",
|
||||
"audio_settings_fec_title": "FEC (Correction d'Erreur)",
|
||||
"audio_settings_hdmi_label": "HDMI",
|
||||
"audio_settings_output_description": "Activer ou désactiver l'audio de l'ordinateur distant",
|
||||
"audio_settings_output_source_description": "Sélectionnez le périphérique de capture audio (HDMI ou USB)",
|
||||
"audio_settings_output_source_failed": "Échec de la configuration de la source de sortie audio : {error}",
|
||||
"audio_settings_output_source_success": "Source de sortie audio mise à jour avec succès",
|
||||
"audio_settings_output_source_title": "Source de sortie audio",
|
||||
"audio_settings_output_title": "Sortie audio",
|
||||
"audio_settings_title": "Audio",
|
||||
"audio_settings_usb_label": "USB",
|
||||
"audio_speakers_description": "Audio de la cible vers les haut-parleurs",
|
||||
"audio_speakers_title": "Haut-parleurs",
|
||||
"auth_authentication_mode": "Veuillez sélectionner un mode d'authentification",
|
||||
"auth_authentication_mode_error": "Une erreur s'est produite lors de la définition du mode d'authentification",
|
||||
"auth_authentication_mode_invalid": "Mode d'authentification non valide",
|
||||
|
|
@ -223,6 +261,9 @@
|
|||
"connection_stats_packets_lost_description": "Nombre de paquets vidéo RTP entrants perdus.",
|
||||
"connection_stats_playback_delay": "Délai de lecture",
|
||||
"connection_stats_playback_delay_description": "Retard ajouté par le tampon de gigue pour fluidifier la lecture lorsque les images arrivent de manière inégale.",
|
||||
"connection_stats_remote_ip_address": "Adresse IP distante",
|
||||
"connection_stats_remote_ip_address_copy_error": "Échec de la copie de l'adresse IP distante",
|
||||
"connection_stats_remote_ip_address_copy_success": "Adresse IP distante { ip } copiée dans le presse-papiers",
|
||||
"connection_stats_round_trip_time": "Temps de trajet aller-retour",
|
||||
"connection_stats_round_trip_time_description": "Temps de trajet aller-retour pour la paire de candidats ICE actifs entre pairs.",
|
||||
"connection_stats_sidebar": "Statistiques de connexion",
|
||||
|
|
@ -748,6 +789,9 @@
|
|||
"peer_connection_failed": "La connexion a échoué",
|
||||
"peer_connection_new": "Nouveau",
|
||||
"previous": "Précédent",
|
||||
"public_ip_card_header": "Adresses IP publiques",
|
||||
"public_ip_card_refresh": "Rafraîchir",
|
||||
"public_ip_card_refresh_error": "Échec de l'actualisation des adresses IP publiques : {error}",
|
||||
"register_device_error": "Une erreur {error} s'est produite lors de l'enregistrement de votre appareil.",
|
||||
"register_device_finish_button": "Terminer la configuration",
|
||||
"register_device_name_description": "Nommez votre appareil pour pouvoir l'identifier facilement plus tard. Vous pouvez modifier ce nom à tout moment.",
|
||||
|
|
|
|||
|
|
@ -49,8 +49,6 @@
|
|||
"access_update_tls_settings": "Aggiorna le impostazioni TLS",
|
||||
"action_bar_audio": "Audio",
|
||||
"action_bar_connection_stats": "Statistiche di connessione",
|
||||
"audio_input_disabled": "Ingresso audio disabilitato",
|
||||
"audio_input_enabled": "Ingresso audio abilitato",
|
||||
"audio_input_failed_disable": "Impossibile disabilitare l'ingresso audio: {error}",
|
||||
"audio_input_failed_enable": "Impossibile abilitare l'ingresso audio: {error}",
|
||||
"audio_input_auto_enable_disabled": "Abilitazione automatica microfono disabilitata",
|
||||
|
|
@ -167,6 +165,46 @@
|
|||
"atx_power_control_reset_button": "Reset",
|
||||
"atx_power_control_send_action_error": "Impossibile inviare l'azione di alimentazione ATX {action} : {error}",
|
||||
"atx_power_control_short_power_button": "Pressione breve",
|
||||
"audio_https_only": "Solo HTTPS",
|
||||
"audio_input_auto_enable_disabled": "Abilitazione automatica microfono disabilitata",
|
||||
"audio_input_auto_enable_enabled": "Abilitazione automatica microfono abilitata",
|
||||
"audio_input_failed_disable": "Impossibile disabilitare l'ingresso audio: {error}",
|
||||
"audio_input_failed_enable": "Impossibile abilitare l'ingresso audio: {error}",
|
||||
"audio_microphone_description": "Ingresso microfono al target",
|
||||
"audio_microphone_title": "Microfono",
|
||||
"audio_output_disabled": "Uscita audio disabilitata",
|
||||
"audio_output_enabled": "Uscita audio abilitata",
|
||||
"audio_output_failed_disable": "Impossibile disabilitare l'uscita audio: {error}",
|
||||
"audio_output_failed_enable": "Impossibile abilitare l'uscita audio: {error}",
|
||||
"audio_popover_description": "Controlli audio rapidi per altoparlanti e microfono",
|
||||
"audio_popover_title": "Audio",
|
||||
"audio_settings_applied": "Impostazioni audio applicate",
|
||||
"audio_settings_apply_button": "Applica impostazioni",
|
||||
"audio_settings_auto_enable_microphone_description": "Abilita automaticamente il microfono del browser durante la connessione (altrimenti devi abilitarlo manualmente ad ogni sessione)",
|
||||
"audio_settings_auto_enable_microphone_title": "Abilita automaticamente il microfono",
|
||||
"audio_settings_bitrate_description": "Bitrate di codifica audio (più alto = migliore qualità, più banda)",
|
||||
"audio_settings_bitrate_title": "Bitrate Opus",
|
||||
"audio_settings_buffer_description": "Dimensione buffer ALSA (più alto = più stabile, più latenza)",
|
||||
"audio_settings_buffer_title": "Periodi Buffer",
|
||||
"audio_settings_complexity_description": "Complessità dell'encoder (0-10, più alto = migliore qualità, più CPU)",
|
||||
"audio_settings_complexity_title": "Complessità Opus",
|
||||
"audio_settings_config_updated": "Configurazione audio aggiornata",
|
||||
"audio_settings_description": "Configura le impostazioni di ingresso e uscita audio per il tuo dispositivo JetKVM",
|
||||
"audio_settings_dtx_description": "Risparmia banda durante il silenzio",
|
||||
"audio_settings_dtx_title": "DTX (Trasmissione Discontinua)",
|
||||
"audio_settings_fec_description": "Migliora la qualità audio su connessioni con perdita",
|
||||
"audio_settings_fec_title": "FEC (Correzione Errori)",
|
||||
"audio_settings_hdmi_label": "HDMI",
|
||||
"audio_settings_output_description": "Abilita o disabilita l'audio dal computer remoto",
|
||||
"audio_settings_output_source_description": "Seleziona il dispositivo di acquisizione audio (HDMI o USB)",
|
||||
"audio_settings_output_source_failed": "Impossibile impostare la sorgente di uscita audio: {error}",
|
||||
"audio_settings_output_source_success": "Sorgente di uscita audio aggiornata con successo",
|
||||
"audio_settings_output_source_title": "Sorgente di uscita audio",
|
||||
"audio_settings_output_title": "Uscita audio",
|
||||
"audio_settings_title": "Audio",
|
||||
"audio_settings_usb_label": "USB",
|
||||
"audio_speakers_description": "Audio dal target agli altoparlanti",
|
||||
"audio_speakers_title": "Altoparlanti",
|
||||
"auth_authentication_mode": "Seleziona una modalità di autenticazione",
|
||||
"auth_authentication_mode_error": "Si è verificato un errore durante l'impostazione della modalità di autenticazione",
|
||||
"auth_authentication_mode_invalid": "Modalità di autenticazione non valida",
|
||||
|
|
@ -223,6 +261,9 @@
|
|||
"connection_stats_packets_lost_description": "Conteggio dei pacchetti video RTP in entrata persi.",
|
||||
"connection_stats_playback_delay": "Ritardo di riproduzione",
|
||||
"connection_stats_playback_delay_description": "Ritardo aggiunto dal buffer jitter per rendere più fluida la riproduzione quando i fotogrammi arrivano in modo non uniforme.",
|
||||
"connection_stats_remote_ip_address": "Indirizzo IP remoto",
|
||||
"connection_stats_remote_ip_address_copy_error": "Impossibile copiare l'indirizzo IP remoto",
|
||||
"connection_stats_remote_ip_address_copy_success": "Indirizzo IP remoto { ip } copiato negli appunti",
|
||||
"connection_stats_round_trip_time": "Tempo di andata e ritorno",
|
||||
"connection_stats_round_trip_time_description": "Tempo di andata e ritorno per la coppia di candidati ICE attivi tra pari.",
|
||||
"connection_stats_sidebar": "Statistiche di connessione",
|
||||
|
|
@ -748,6 +789,9 @@
|
|||
"peer_connection_failed": "Connessione fallita",
|
||||
"peer_connection_new": "Collegamento",
|
||||
"previous": "Precedente",
|
||||
"public_ip_card_header": "Indirizzi IP pubblici",
|
||||
"public_ip_card_refresh": "Aggiorna",
|
||||
"public_ip_card_refresh_error": "Impossibile aggiornare gli indirizzi IP pubblici: {error}",
|
||||
"register_device_error": "Si è verificato un errore {error} durante la registrazione del dispositivo.",
|
||||
"register_device_finish_button": "Completa l'installazione",
|
||||
"register_device_name_description": "Assegna un nome al tuo dispositivo per poterlo identificare facilmente in seguito. Puoi cambiare questo nome in qualsiasi momento.",
|
||||
|
|
|
|||
|
|
@ -49,8 +49,6 @@
|
|||
"access_update_tls_settings": "Oppdater TLS-innstillinger",
|
||||
"action_bar_audio": "Lyd",
|
||||
"action_bar_connection_stats": "Tilkoblingsstatistikk",
|
||||
"audio_input_disabled": "Lydinngang deaktivert",
|
||||
"audio_input_enabled": "Lydinngang aktivert",
|
||||
"audio_input_failed_disable": "Kunne ikke deaktivere lydinngang: {error}",
|
||||
"audio_input_failed_enable": "Kunne ikke aktivere lydinngang: {error}",
|
||||
"audio_input_auto_enable_disabled": "Automatisk aktivering av mikrofon deaktivert",
|
||||
|
|
@ -167,6 +165,46 @@
|
|||
"atx_power_control_reset_button": "Tilbakestill",
|
||||
"atx_power_control_send_action_error": "Kunne ikke sende ATX-strømhandling {action} : {error}",
|
||||
"atx_power_control_short_power_button": "Kort trykk",
|
||||
"audio_https_only": "Kun HTTPS",
|
||||
"audio_input_auto_enable_disabled": "Automatisk aktivering av mikrofon deaktivert",
|
||||
"audio_input_auto_enable_enabled": "Automatisk aktivering av mikrofon aktivert",
|
||||
"audio_input_failed_disable": "Kunne ikke deaktivere lydinngang: {error}",
|
||||
"audio_input_failed_enable": "Kunne ikke aktivere lydinngang: {error}",
|
||||
"audio_microphone_description": "Mikrofoninngang til mål",
|
||||
"audio_microphone_title": "Mikrofon",
|
||||
"audio_output_disabled": "Lydutgang deaktivert",
|
||||
"audio_output_enabled": "Lydutgang aktivert",
|
||||
"audio_output_failed_disable": "Kunne ikke deaktivere lydutgang: {error}",
|
||||
"audio_output_failed_enable": "Kunne ikke aktivere lydutgang: {error}",
|
||||
"audio_popover_description": "Raske lydkontroller for høyttalere og mikrofon",
|
||||
"audio_popover_title": "Lyd",
|
||||
"audio_settings_applied": "Lydinnstillinger brukt",
|
||||
"audio_settings_apply_button": "Bruk innstillinger",
|
||||
"audio_settings_auto_enable_microphone_description": "Aktiver automatisk nettlesermikrofon ved tilkobling (ellers må du aktivere det manuelt hver økt)",
|
||||
"audio_settings_auto_enable_microphone_title": "Aktiver mikrofon automatisk",
|
||||
"audio_settings_bitrate_description": "Lydkodingsbitrate (høyere = bedre kvalitet, mer båndbredde)",
|
||||
"audio_settings_bitrate_title": "Opus Bitrate",
|
||||
"audio_settings_buffer_description": "ALSA bufferstørrelse (høyere = mer stabil, mer latens)",
|
||||
"audio_settings_buffer_title": "Bufferperioder",
|
||||
"audio_settings_complexity_description": "Encoder-kompleksitet (0-10, høyere = bedre kvalitet, mer CPU)",
|
||||
"audio_settings_complexity_title": "Opus Kompleksitet",
|
||||
"audio_settings_config_updated": "Lydkonfigurasjon oppdatert",
|
||||
"audio_settings_description": "Konfigurer lydinngangs- og lydutgangsinnstillinger for JetKVM-enheten din",
|
||||
"audio_settings_dtx_description": "Spar båndbredde under stillhet",
|
||||
"audio_settings_dtx_title": "DTX (Diskontinuerlig Overføring)",
|
||||
"audio_settings_fec_description": "Forbedre lydkvaliteten på tapende tilkoblinger",
|
||||
"audio_settings_fec_title": "FEC (Fremadrettet Feilkorreksjon)",
|
||||
"audio_settings_hdmi_label": "HDMI",
|
||||
"audio_settings_output_description": "Aktiver eller deaktiver lyd fra den eksterne datamaskinen",
|
||||
"audio_settings_output_source_description": "Velg lydopptaksenhet (HDMI eller USB)",
|
||||
"audio_settings_output_source_failed": "Kunne ikke angi lydutgangskilde: {error}",
|
||||
"audio_settings_output_source_success": "Lydutgangskilde oppdatert vellykket",
|
||||
"audio_settings_output_source_title": "Lydutgangskilde",
|
||||
"audio_settings_output_title": "Lydutgang",
|
||||
"audio_settings_title": "Lyd",
|
||||
"audio_settings_usb_label": "USB",
|
||||
"audio_speakers_description": "Lyd fra mål til høyttalere",
|
||||
"audio_speakers_title": "Høyttalere",
|
||||
"auth_authentication_mode": "Vennligst velg en autentiseringsmodus",
|
||||
"auth_authentication_mode_error": "Det oppsto en feil under angivelse av autentiseringsmodus",
|
||||
"auth_authentication_mode_invalid": "Ugyldig autentiseringsmodus",
|
||||
|
|
@ -223,6 +261,9 @@
|
|||
"connection_stats_packets_lost_description": "Antall tapte innkommende RTP-videopakker.",
|
||||
"connection_stats_playback_delay": "Avspillingsforsinkelse",
|
||||
"connection_stats_playback_delay_description": "Forsinkelse lagt til av jitterbufferen for jevn avspilling når bilder ankommer ujevnt.",
|
||||
"connection_stats_remote_ip_address": "Ekstern IP-adresse",
|
||||
"connection_stats_remote_ip_address_copy_error": "Kunne ikke kopiere den eksterne IP-adressen",
|
||||
"connection_stats_remote_ip_address_copy_success": "Ekstern IP-adresse { ip } kopiert til utklippstavlen",
|
||||
"connection_stats_round_trip_time": "Tur-retur-tid",
|
||||
"connection_stats_round_trip_time_description": "Rundturstid for det aktive ICE-kandidatparet mellom jevnaldrende.",
|
||||
"connection_stats_sidebar": "Tilkoblingsstatistikk",
|
||||
|
|
@ -748,6 +789,9 @@
|
|||
"peer_connection_failed": "Tilkoblingen mislyktes",
|
||||
"peer_connection_new": "Tilkobling",
|
||||
"previous": "Tidligere",
|
||||
"public_ip_card_header": "Offentlige IP-adresser",
|
||||
"public_ip_card_refresh": "Forfriske",
|
||||
"public_ip_card_refresh_error": "Kunne ikke oppdatere offentlige IP-adresser: {error}",
|
||||
"register_device_error": "Det oppsto en feil {error} under registrering av enheten din.",
|
||||
"register_device_finish_button": "Fullfør oppsettet",
|
||||
"register_device_name_description": "Gi enheten din et navn slik at du enkelt kan identifisere den senere. Du kan endre dette navnet når som helst.",
|
||||
|
|
|
|||
|
|
@ -48,9 +48,6 @@
|
|||
"access_tls_updated": "TLS-inställningarna har uppdaterats",
|
||||
"access_update_tls_settings": "Uppdatera TLS-inställningar",
|
||||
"action_bar_audio": "Ljud",
|
||||
"action_bar_connection_stats": "Anslutningsstatistik",
|
||||
"audio_input_disabled": "Ljudingång inaktiverad",
|
||||
"audio_input_enabled": "Ljudingång aktiverad",
|
||||
"audio_input_failed_disable": "Det gick inte att inaktivera ljudingången: {error}",
|
||||
"audio_input_failed_enable": "Det gick inte att aktivera ljudingången: {error}",
|
||||
"audio_input_auto_enable_disabled": "Automatisk aktivering av mikrofon inaktiverad",
|
||||
|
|
@ -167,6 +164,46 @@
|
|||
"atx_power_control_reset_button": "Starta om",
|
||||
"atx_power_control_send_action_error": "Misslyckades med att skicka ATX-strömåtgärd {action} : {error}",
|
||||
"atx_power_control_short_power_button": "Kort tryck",
|
||||
"audio_https_only": "Endast HTTPS",
|
||||
"audio_input_auto_enable_disabled": "Automatisk aktivering av mikrofon inaktiverad",
|
||||
"audio_input_auto_enable_enabled": "Automatisk aktivering av mikrofon aktiverad",
|
||||
"audio_input_failed_disable": "Det gick inte att inaktivera ljudingången: {error}",
|
||||
"audio_input_failed_enable": "Det gick inte att aktivera ljudingången: {error}",
|
||||
"audio_microphone_description": "Mikrofoningång till mål",
|
||||
"audio_microphone_title": "Mikrofon",
|
||||
"audio_output_disabled": "Ljudutgång inaktiverad",
|
||||
"audio_output_enabled": "Ljudutgång aktiverad",
|
||||
"audio_output_failed_disable": "Det gick inte att inaktivera ljudutgången: {error}",
|
||||
"audio_output_failed_enable": "Det gick inte att aktivera ljudutgången: {error}",
|
||||
"audio_popover_description": "Snabba ljudkontroller för högtalare och mikrofon",
|
||||
"audio_popover_title": "Ljud",
|
||||
"audio_settings_applied": "Ljudinställningar tillämpade",
|
||||
"audio_settings_apply_button": "Tillämpa inställningar",
|
||||
"audio_settings_auto_enable_microphone_description": "Aktivera automatiskt webbläsarmikrofon vid anslutning (annars måste du aktivera den manuellt varje session)",
|
||||
"audio_settings_auto_enable_microphone_title": "Aktivera mikrofon automatiskt",
|
||||
"audio_settings_bitrate_description": "Ljudkodningsbitrate (högre = bättre kvalitet, mer bandbredd)",
|
||||
"audio_settings_bitrate_title": "Opus Bitrate",
|
||||
"audio_settings_buffer_description": "ALSA bufferstorlek (högre = mer stabil, mer latens)",
|
||||
"audio_settings_buffer_title": "Bufferperioder",
|
||||
"audio_settings_complexity_description": "Encoder-komplexitet (0-10, högre = bättre kvalitet, mer CPU)",
|
||||
"audio_settings_complexity_title": "Opus Komplexitet",
|
||||
"audio_settings_config_updated": "Ljudkonfiguration uppdaterad",
|
||||
"audio_settings_description": "Konfigurera ljudinmatnings- och ljudutgångsinställningar för din JetKVM-enhet",
|
||||
"audio_settings_dtx_description": "Spara bandbredd under tystnad",
|
||||
"audio_settings_dtx_title": "DTX (Diskontinuerlig Överföring)",
|
||||
"audio_settings_fec_description": "Förbättra ljudkvaliteten på förlustdrabbade anslutningar",
|
||||
"audio_settings_fec_title": "FEC (Framåtriktad Felkorrigering)",
|
||||
"audio_settings_hdmi_label": "HDMI",
|
||||
"audio_settings_output_description": "Aktivera eller inaktivera ljud från fjärrdatorn",
|
||||
"audio_settings_output_source_description": "Välj ljudinspelningsenhet (HDMI eller USB)",
|
||||
"audio_settings_output_source_failed": "Det gick inte att ställa in ljudutgångskälla: {error}",
|
||||
"audio_settings_output_source_success": "Ljudutgångskälla uppdaterades framgångsrikt",
|
||||
"audio_settings_output_source_title": "Ljudutgångskälla",
|
||||
"audio_settings_output_title": "Ljudutgång",
|
||||
"audio_settings_title": "Ljud",
|
||||
"audio_settings_usb_label": "USB",
|
||||
"audio_speakers_description": "Ljud från mål till högtalare",
|
||||
"audio_speakers_title": "Högtalare",
|
||||
"auth_authentication_mode": "Välj ett autentiseringsläge",
|
||||
"auth_authentication_mode_error": "Ett fel uppstod när autentiseringsläget ställdes in",
|
||||
"auth_authentication_mode_invalid": "Ogiltigt autentiseringsläge",
|
||||
|
|
@ -223,6 +260,9 @@
|
|||
"connection_stats_packets_lost_description": "Antal förlorade inkommande RTP-videopaket.",
|
||||
"connection_stats_playback_delay": "Uppspelningsfördröjning",
|
||||
"connection_stats_playback_delay_description": "Fördröjning som läggs till av jitterbufferten för att jämna ut uppspelningen när bildrutor anländer ojämnt.",
|
||||
"connection_stats_remote_ip_address": "Fjärr-IP-adress",
|
||||
"connection_stats_remote_ip_address_copy_error": "Misslyckades med att kopiera fjärr-IP-adressen",
|
||||
"connection_stats_remote_ip_address_copy_success": "Fjärr-IP-adress { ip } kopierad till urklipp",
|
||||
"connection_stats_round_trip_time": "Tur- och returtid",
|
||||
"connection_stats_round_trip_time_description": "Tur- och returtid för det aktiva ICE-kandidatparet mellan peers.",
|
||||
"connection_stats_sidebar": "Anslutningsstatistik",
|
||||
|
|
@ -748,6 +788,9 @@
|
|||
"peer_connection_failed": "Anslutningen misslyckades",
|
||||
"peer_connection_new": "Ansluter",
|
||||
"previous": "Föregående",
|
||||
"public_ip_card_header": "Offentliga IP-adresser",
|
||||
"public_ip_card_refresh": "Uppdatera",
|
||||
"public_ip_card_refresh_error": "Misslyckades med att uppdatera offentliga IP-adresser: {error}",
|
||||
"register_device_error": "Det uppstod ett fel {error} din enhet registrerades.",
|
||||
"register_device_finish_button": "Slutför installationen",
|
||||
"register_device_name_description": "Namnge din enhet så att du enkelt kan identifiera den senare. Du kan ändra namnet när som helst.",
|
||||
|
|
|
|||
|
|
@ -49,8 +49,6 @@
|
|||
"access_update_tls_settings": "更新 TLS 设置",
|
||||
"action_bar_audio": "音频",
|
||||
"action_bar_connection_stats": "连接统计",
|
||||
"audio_input_disabled": "音频输入已禁用",
|
||||
"audio_input_enabled": "音频输入已启用",
|
||||
"audio_input_failed_disable": "禁用音频输入失败:{error}",
|
||||
"audio_input_failed_enable": "启用音频输入失败:{error}",
|
||||
"audio_input_auto_enable_disabled": "自动启用麦克风已禁用",
|
||||
|
|
@ -167,6 +165,46 @@
|
|||
"atx_power_control_reset_button": "重置",
|
||||
"atx_power_control_send_action_error": "无法发送 ATX 电源操作 {action} : {error}",
|
||||
"atx_power_control_short_power_button": "短按",
|
||||
"audio_https_only": "仅限 HTTPS",
|
||||
"audio_input_auto_enable_disabled": "自动启用麦克风已禁用",
|
||||
"audio_input_auto_enable_enabled": "自动启用麦克风已启用",
|
||||
"audio_input_failed_disable": "禁用音频输入失败:{error}",
|
||||
"audio_input_failed_enable": "启用音频输入失败:{error}",
|
||||
"audio_microphone_description": "麦克风输入到目标设备",
|
||||
"audio_microphone_title": "麦克风",
|
||||
"audio_output_disabled": "音频输出已禁用",
|
||||
"audio_output_enabled": "音频输出已启用",
|
||||
"audio_output_failed_disable": "禁用音频输出失败:{error}",
|
||||
"audio_output_failed_enable": "启用音频输出失败:{error}",
|
||||
"audio_popover_description": "扬声器和麦克风的快速音频控制",
|
||||
"audio_popover_title": "音频",
|
||||
"audio_settings_applied": "音频设置已应用",
|
||||
"audio_settings_apply_button": "应用设置",
|
||||
"audio_settings_auto_enable_microphone_description": "连接时自动启用浏览器麦克风(否则您必须在每次会话中手动启用)",
|
||||
"audio_settings_auto_enable_microphone_title": "自动启用麦克风",
|
||||
"audio_settings_bitrate_description": "音频编码比特率(越高 = 质量越好,带宽越大)",
|
||||
"audio_settings_bitrate_title": "Opus 比特率",
|
||||
"audio_settings_buffer_description": "ALSA 缓冲大小(越高 = 越稳定,延迟越高)",
|
||||
"audio_settings_buffer_title": "缓冲周期",
|
||||
"audio_settings_complexity_description": "编码器复杂度(0-10,越高 = 质量越好,CPU 使用越多)",
|
||||
"audio_settings_complexity_title": "Opus 复杂度",
|
||||
"audio_settings_config_updated": "音频配置已更新",
|
||||
"audio_settings_description": "配置 JetKVM 设备的音频输入和输出设置",
|
||||
"audio_settings_dtx_description": "在静音时节省带宽",
|
||||
"audio_settings_dtx_title": "DTX(不连续传输)",
|
||||
"audio_settings_fec_description": "改善有损连接上的音频质量",
|
||||
"audio_settings_fec_title": "FEC(前向纠错)",
|
||||
"audio_settings_hdmi_label": "HDMI",
|
||||
"audio_settings_output_description": "启用或禁用来自远程计算机的音频",
|
||||
"audio_settings_output_source_description": "选择音频捕获设备(HDMI 或 USB)",
|
||||
"audio_settings_output_source_failed": "设置音频输出源失败:{error}",
|
||||
"audio_settings_output_source_success": "音频输出源更新成功",
|
||||
"audio_settings_output_source_title": "音频输出源",
|
||||
"audio_settings_output_title": "音频输出",
|
||||
"audio_settings_title": "音频",
|
||||
"audio_settings_usb_label": "USB",
|
||||
"audio_speakers_description": "从目标设备到扬声器的音频",
|
||||
"audio_speakers_title": "扬声器",
|
||||
"auth_authentication_mode": "请选择身份验证方式",
|
||||
"auth_authentication_mode_error": "设置身份验证模式时发生错误",
|
||||
"auth_authentication_mode_invalid": "身份验证模式无效",
|
||||
|
|
@ -223,6 +261,9 @@
|
|||
"connection_stats_packets_lost_description": "丢失的入站视频 RTP 数据包的数量。",
|
||||
"connection_stats_playback_delay": "播放延迟",
|
||||
"connection_stats_playback_delay_description": "当帧不均匀到达时,抖动缓冲区添加延迟以平滑播放。",
|
||||
"connection_stats_remote_ip_address": "远程IP地址",
|
||||
"connection_stats_remote_ip_address_copy_error": "复制远程 IP 地址失败",
|
||||
"connection_stats_remote_ip_address_copy_success": "远程 IP 地址{ ip }已复制到剪贴板",
|
||||
"connection_stats_round_trip_time": "往返时间",
|
||||
"connection_stats_round_trip_time_description": "对等体之间活跃 ICE 候选对的往返时间。",
|
||||
"connection_stats_sidebar": "连接统计",
|
||||
|
|
@ -748,6 +789,9 @@
|
|||
"peer_connection_failed": "连接失败",
|
||||
"peer_connection_new": "正在连接",
|
||||
"previous": "上一步",
|
||||
"public_ip_card_header": "公共 IP 地址",
|
||||
"public_ip_card_refresh": "刷新",
|
||||
"public_ip_card_refresh_error": "刷新公网 IP 地址失败: {error}",
|
||||
"register_device_error": "注册您的设备时出现错误{error} 。",
|
||||
"register_device_finish_button": "完成设置",
|
||||
"register_device_name_description": "为您的设备命名,以便日后轻松识别。您可以随时更改此名称。",
|
||||
|
|
|
|||
Loading…
Reference in New Issue