mirror of https://github.com/jetkvm/kvm.git
Merge branch 'dev' into nevexo/display-brightness
This commit is contained in:
commit
e177fdb1cd
16
cloud.go
16
cloud.go
|
@ -7,13 +7,14 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"github.com/coder/websocket/wsjson"
|
||||
"time"
|
||||
|
||||
"github.com/coder/websocket/wsjson"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/coder/websocket"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type CloudRegisterRequest struct {
|
||||
|
@ -68,6 +69,11 @@ func handleCloudRegister(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if config.CloudToken == "" {
|
||||
logger.Info("Starting websocket client due to adoption")
|
||||
go RunWebsocketClient()
|
||||
}
|
||||
|
||||
config.CloudToken = tokenResp.SecretToken
|
||||
config.CloudURL = req.CloudAPI
|
||||
|
||||
|
@ -187,7 +193,11 @@ func handleSessionRequest(ctx context.Context, c *websocket.Conn, req WebRTCSess
|
|||
return fmt.Errorf("google identity mismatch")
|
||||
}
|
||||
|
||||
session, err := newSession()
|
||||
session, err := newSession(SessionConfig{
|
||||
ICEServers: req.ICEServers,
|
||||
LocalIP: req.IP,
|
||||
IsCloud: true,
|
||||
})
|
||||
if err != nil {
|
||||
_ = wsjson.Write(context.Background(), c, gin.H{"error": err})
|
||||
return err
|
||||
|
|
|
@ -22,7 +22,8 @@ type Config struct {
|
|||
LocalAuthToken string `json:"local_auth_token"`
|
||||
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
|
||||
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
|
||||
DisplayMaxBrightness int `json:"display_max_brightness"`
|
||||
EdidString string `json:"hdmi_edid_string"`
|
||||
DisplayMaxBrightness int `json:"display_max_brightness"`
|
||||
DisplayDimAfterSec int `json:"display_dim_after_sec"`
|
||||
DisplayOffAfterSec int `json:"display_off_after_sec"`
|
||||
}
|
||||
|
|
|
@ -189,6 +189,12 @@ func rpcSetEDID(edid string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save EDID to config, allowing it to be restored on reboot.
|
||||
LoadConfig()
|
||||
config.EdidString = edid
|
||||
SaveConfig()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
6
main.go
6
main.go
|
@ -66,7 +66,11 @@ func Main() {
|
|||
}()
|
||||
//go RunFuseServer()
|
||||
go RunWebServer()
|
||||
go RunWebsocketClient()
|
||||
// If the cloud token isn't set, the client won't be started by default.
|
||||
// However, if the user adopts the device via the web interface, handleCloudRegister will start the client.
|
||||
if config.CloudToken != "" {
|
||||
go RunWebsocketClient()
|
||||
}
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigs
|
||||
|
|
23
native.go
23
native.go
|
@ -11,6 +11,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pion/webrtc/v4/pkg/media"
|
||||
|
@ -151,6 +152,9 @@ func handleCtrlClient(conn net.Conn) {
|
|||
|
||||
ctrlSocketConn = conn
|
||||
|
||||
// Restore HDMI EDID if applicable
|
||||
go restoreHdmiEdid()
|
||||
|
||||
readBuf := make([]byte, 4096)
|
||||
for {
|
||||
n, err := conn.Read(readBuf)
|
||||
|
@ -224,6 +228,12 @@ func ExtractAndRunNativeBin() error {
|
|||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// Set the process group ID so we can kill the process and its children when this process exits
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
Pdeathsig: syscall.SIGKILL,
|
||||
}
|
||||
|
||||
// Start the command
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start binary: %w", err)
|
||||
|
@ -297,3 +307,16 @@ func ensureBinaryUpdated(destPath string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore the HDMI EDID value from the config.
|
||||
// Called after successful connection to jetkvm_native.
|
||||
func restoreHdmiEdid() {
|
||||
LoadConfig()
|
||||
if config.EdidString != "" {
|
||||
logger.Infof("Restoring HDMI EDID to %v", config.EdidString)
|
||||
_, err := CallCtrlAction("set_edid", map[string]interface{}{"edid": config.EdidString})
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to restore HDMI EDID: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
58
network.go
58
network.go
|
@ -6,12 +6,15 @@ import (
|
|||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
"net"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
)
|
||||
|
||||
var mDNSConn *mdns.Conn
|
||||
|
||||
var networkState struct {
|
||||
Up bool
|
||||
IPv4 string
|
||||
|
@ -25,6 +28,23 @@ type LocalIpInfo struct {
|
|||
MAC string
|
||||
}
|
||||
|
||||
// setDhcpClientState sends signals to udhcpc to change it's current mode
|
||||
// of operation. Setting active to true will force udhcpc to renew the DHCP lease.
|
||||
// Setting active to false will put udhcpc into idle mode.
|
||||
func setDhcpClientState(active bool) {
|
||||
var signal string;
|
||||
if active {
|
||||
signal = "-SIGUSR1"
|
||||
} else {
|
||||
signal = "-SIGUSR2"
|
||||
}
|
||||
|
||||
cmd := exec.Command("/usr/bin/killall", signal, "udhcpc");
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Printf("network: setDhcpClientState: failed to change udhcpc state: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func checkNetworkState() {
|
||||
iface, err := netlink.LinkByName("eth0")
|
||||
if err != nil {
|
||||
|
@ -47,22 +67,52 @@ func checkNetworkState() {
|
|||
fmt.Printf("failed to get addresses for eth0: %v\n", err)
|
||||
}
|
||||
|
||||
// If the link is going down, put udhcpc into idle mode.
|
||||
// If the link is coming back up, activate udhcpc and force it to renew the lease.
|
||||
if newState.Up != networkState.Up {
|
||||
setDhcpClientState(newState.Up)
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
if addr.IP.To4() != nil {
|
||||
newState.IPv4 = addr.IP.String()
|
||||
if !newState.Up && networkState.Up {
|
||||
// If the network is going down, remove all IPv4 addresses from the interface.
|
||||
fmt.Printf("network: state transitioned to down, removing IPv4 address %s\n", addr.IP.String())
|
||||
err := netlink.AddrDel(iface, &addr)
|
||||
if err != nil {
|
||||
fmt.Printf("network: failed to delete %s", addr.IP.String())
|
||||
}
|
||||
|
||||
newState.IPv4 = "..."
|
||||
} else {
|
||||
newState.IPv4 = addr.IP.String()
|
||||
}
|
||||
} else if addr.IP.To16() != nil && newState.IPv6 == "" {
|
||||
newState.IPv6 = addr.IP.String()
|
||||
}
|
||||
}
|
||||
|
||||
if newState != networkState {
|
||||
networkState = newState
|
||||
fmt.Println("network state changed")
|
||||
//restart MDNS
|
||||
startMDNS()
|
||||
networkState = newState
|
||||
requestDisplayUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
func startMDNS() error {
|
||||
//If server was previously running, stop it
|
||||
if mDNSConn != nil {
|
||||
fmt.Printf("Stopping mDNS server\n")
|
||||
err := mDNSConn.Close()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to stop mDNS server: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
//Start a new server
|
||||
fmt.Printf("Starting mDNS server on jetkvm.local\n")
|
||||
addr4, err := net.ResolveUDPAddr("udp4", mdns.DefaultAddressIPv4)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -83,10 +133,11 @@ func startMDNS() error {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = mdns.Server(ipv4.NewPacketConn(l4), ipv6.NewPacketConn(l6), &mdns.Config{
|
||||
mDNSConn, err = mdns.Server(ipv4.NewPacketConn(l4), ipv6.NewPacketConn(l6), &mdns.Config{
|
||||
LocalNames: []string{"jetkvm.local"}, //TODO: make it configurable
|
||||
})
|
||||
if err != nil {
|
||||
mDNSConn = nil
|
||||
return err
|
||||
}
|
||||
//defer server.Close()
|
||||
|
@ -122,7 +173,6 @@ func init() {
|
|||
}
|
||||
}
|
||||
}()
|
||||
fmt.Println("Starting mDNS server")
|
||||
err := startMDNS()
|
||||
if err != nil {
|
||||
fmt.Println("failed to run mDNS: %v", err)
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"dev": "vite dev --mode=development",
|
||||
"build": "npm run build:prod",
|
||||
"build:device": "tsc && vite build --mode=device --emptyOutDir",
|
||||
"dev:device": "vite dev --mode=device",
|
||||
"build:prod": "tsc && vite build --mode=production",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
||||
},
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
useMountMediaStore,
|
||||
useUiStore,
|
||||
useSettingsStore,
|
||||
useVideoStore,
|
||||
} from "@/hooks/stores";
|
||||
import { MdOutlineContentPasteGo } from "react-icons/md";
|
||||
import Container from "@components/Container";
|
||||
|
@ -33,6 +34,7 @@ export default function Actionbar({
|
|||
state => state.remoteVirtualMediaState,
|
||||
);
|
||||
const developerMode = useSettingsStore(state => state.developerMode);
|
||||
const hdmiState = useVideoStore(state => state.hdmiState);
|
||||
|
||||
// This is the only way to get a reliable state change for the popover
|
||||
// at time of writing this there is no mount, or unmount event for the popover
|
||||
|
@ -247,6 +249,7 @@ export default function Actionbar({
|
|||
size="XS"
|
||||
theme="light"
|
||||
text="Fullscreen"
|
||||
disabled={hdmiState !== 'ready'}
|
||||
LeadingIcon={LuMaximize}
|
||||
onClick={() => requestFullscreen()}
|
||||
/>
|
||||
|
|
|
@ -26,6 +26,7 @@ import { InputFieldWithLabel } from "./InputField";
|
|||
import DebianIcon from "@/assets/debian-icon.png";
|
||||
import UbuntuIcon from "@/assets/ubuntu-icon.png";
|
||||
import FedoraIcon from "@/assets/fedora-icon.png";
|
||||
import OpenSUSEIcon from "@/assets/opensuse-icon.png";
|
||||
import ArchIcon from "@/assets/arch-icon.png";
|
||||
import NetBootIcon from "@/assets/netboot-icon.svg";
|
||||
import { TrashIcon } from "@heroicons/react/16/solid";
|
||||
|
@ -534,17 +535,27 @@ function UrlView({
|
|||
},
|
||||
{
|
||||
name: "Debian 12",
|
||||
url: "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.7.0-amd64-netinst.iso",
|
||||
url: "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.9.0-amd64-netinst.iso",
|
||||
icon: DebianIcon,
|
||||
},
|
||||
{
|
||||
name: "Fedora 38",
|
||||
url: "https://mirror.ihost.md/fedora/releases/38/Workstation/x86_64/iso/Fedora-Workstation-Live-x86_64-38-1.6.iso",
|
||||
name: "Fedora 41",
|
||||
url: "https://download.fedoraproject.org/pub/fedora/linux/releases/41/Workstation/x86_64/iso/Fedora-Workstation-Live-x86_64-41-1.4.iso",
|
||||
icon: FedoraIcon,
|
||||
},
|
||||
{
|
||||
name: "openSUSE Leap 15.6",
|
||||
url: "https://download.opensuse.org/distribution/leap/15.6/iso/openSUSE-Leap-15.6-NET-x86_64-Media.iso",
|
||||
icon: OpenSUSEIcon,
|
||||
},
|
||||
{
|
||||
name: "openSUSE Tumbleweed",
|
||||
url: "https://download.opensuse.org/tumbleweed/iso/openSUSE-Tumbleweed-NET-x86_64-Current.iso",
|
||||
icon: OpenSUSEIcon,
|
||||
},
|
||||
{
|
||||
name: "Arch Linux",
|
||||
url: "https://archlinux.doridian.net/iso/2024.10.01/archlinux-2024.10.01-x86_64.iso",
|
||||
url: "https://archlinux.doridian.net/iso/2025.02.01/archlinux-2025.02.01-x86_64.iso",
|
||||
icon: ArchIcon,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -30,6 +30,8 @@ export default function WebRTCVideo() {
|
|||
const {
|
||||
setClientSize: setVideoClientSize,
|
||||
setSize: setVideoSize,
|
||||
width: videoWidth,
|
||||
height: videoHeight,
|
||||
clientWidth: videoClientWidth,
|
||||
clientHeight: videoClientHeight,
|
||||
} = useVideoStore();
|
||||
|
@ -102,20 +104,43 @@ export default function WebRTCVideo() {
|
|||
const mouseMoveHandler = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
if (!videoClientWidth || !videoClientHeight) return;
|
||||
const { buttons } = e;
|
||||
// Get the aspect ratios of the video element and the video stream
|
||||
const videoElementAspectRatio = videoClientWidth / videoClientHeight;
|
||||
const videoStreamAspectRatio = videoWidth / videoHeight;
|
||||
|
||||
// Clamp mouse position within the video boundaries
|
||||
const currMouseX = Math.min(Math.max(1, e.offsetX), videoClientWidth);
|
||||
const currMouseY = Math.min(Math.max(1, e.offsetY), videoClientHeight);
|
||||
// Calculate the effective video display area
|
||||
let effectiveWidth = videoClientWidth;
|
||||
let effectiveHeight = videoClientHeight;
|
||||
let offsetX = 0;
|
||||
let offsetY = 0;
|
||||
|
||||
// Normalize mouse position to 0-32767 range (HID absolute coordinate system)
|
||||
const x = Math.round((currMouseX / videoClientWidth) * 32767);
|
||||
const y = Math.round((currMouseY / videoClientHeight) * 32767);
|
||||
if (videoElementAspectRatio > videoStreamAspectRatio) {
|
||||
// Pillarboxing: black bars on the left and right
|
||||
effectiveWidth = videoClientHeight * videoStreamAspectRatio;
|
||||
offsetX = (videoClientWidth - effectiveWidth) / 2;
|
||||
} else if (videoElementAspectRatio < videoStreamAspectRatio) {
|
||||
// Letterboxing: black bars on the top and bottom
|
||||
effectiveHeight = videoClientWidth / videoStreamAspectRatio;
|
||||
offsetY = (videoClientHeight - effectiveHeight) / 2;
|
||||
}
|
||||
|
||||
// Clamp mouse position within the effective video boundaries
|
||||
const clampedX = Math.min(Math.max(offsetX, e.offsetX), offsetX + effectiveWidth);
|
||||
const clampedY = Math.min(Math.max(offsetY, e.offsetY), offsetY + effectiveHeight);
|
||||
|
||||
// Map clamped mouse position to the video stream's coordinate system
|
||||
const relativeX = (clampedX - offsetX) / effectiveWidth;
|
||||
const relativeY = (clampedY - offsetY) / effectiveHeight;
|
||||
|
||||
// Convert to HID absolute coordinate system (0-32767 range)
|
||||
const x = Math.round(relativeX * 32767);
|
||||
const y = Math.round(relativeY * 32767);
|
||||
|
||||
// Send mouse movement
|
||||
const { buttons } = e;
|
||||
sendMouseMovement(x, y, buttons);
|
||||
},
|
||||
[sendMouseMovement, videoClientHeight, videoClientWidth],
|
||||
[sendMouseMovement, videoClientHeight, videoClientWidth, videoWidth, videoHeight],
|
||||
);
|
||||
|
||||
const mouseWheelHandler = useCallback(
|
||||
|
@ -425,7 +450,7 @@ export default function WebRTCVideo() {
|
|||
disablePictureInPicture
|
||||
controlsList="nofullscreen"
|
||||
className={cx(
|
||||
"outline-50 max-h-full max-w-full rounded-md object-contain transition-all duration-1000",
|
||||
"outline-50 max-h-full max-w-full object-contain transition-all duration-1000",
|
||||
{
|
||||
"cursor-none": settings.isCursorHidden,
|
||||
"opacity-0": isLoading || isConnectionError || hdmiError,
|
||||
|
|
|
@ -501,7 +501,7 @@ export default function SettingsSidebar() {
|
|||
<GridCard>
|
||||
<div className="flex items-center px-4 py-3 group gap-x-4">
|
||||
<img
|
||||
className="w-6 shrink-0"
|
||||
className="w-6 shrink-0 dark:invert"
|
||||
src={PointingFinger}
|
||||
alt="Finger touching a screen"
|
||||
/>
|
||||
|
@ -525,7 +525,7 @@ export default function SettingsSidebar() {
|
|||
>
|
||||
<GridCard>
|
||||
<div className="flex items-center px-4 py-3 gap-x-4">
|
||||
<img className="w-6 shrink-0" src={MouseIcon} alt="Mouse icon" />
|
||||
<img className="w-6 shrink-0 dark:invert" src={MouseIcon} alt="Mouse icon" />
|
||||
<div className="flex items-center justify-between grow">
|
||||
<div className="text-left">
|
||||
<h3 className="text-sm font-semibold text-black dark:text-white">
|
||||
|
|
|
@ -2,13 +2,31 @@ import { defineConfig } from "vite";
|
|||
import react from "@vitejs/plugin-react-swc";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
declare const process: {
|
||||
env: {
|
||||
JETKVM_PROXY_URL: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default defineConfig(({ mode, command }) => {
|
||||
const isCloud = mode === "production";
|
||||
const onDevice = mode === "device";
|
||||
const { JETKVM_PROXY_URL } = process.env;
|
||||
|
||||
return {
|
||||
plugins: [tsconfigPaths(), react()],
|
||||
build: { outDir: isCloud ? "dist" : "../static" },
|
||||
server: { host: "0.0.0.0" },
|
||||
base: onDevice ? "/static" : "/",
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
proxy: JETKVM_PROXY_URL ? {
|
||||
'/me': JETKVM_PROXY_URL,
|
||||
'/device': JETKVM_PROXY_URL,
|
||||
'/webrtc': JETKVM_PROXY_URL,
|
||||
'/auth': JETKVM_PROXY_URL,
|
||||
'/storage': JETKVM_PROXY_URL,
|
||||
'/cloud': JETKVM_PROXY_URL,
|
||||
} : undefined
|
||||
},
|
||||
base: onDevice && command === 'build' ? "/static" : "/",
|
||||
};
|
||||
});
|
||||
|
|
4
usb.go
4
usb.go
|
@ -132,7 +132,7 @@ func writeGadgetConfig() error {
|
|||
}
|
||||
err = writeGadgetAttrs(hid0Path, [][]string{
|
||||
{"protocol", "1"},
|
||||
{"subclass", "0"},
|
||||
{"subclass", "1"},
|
||||
{"report_length", "8"},
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -152,7 +152,7 @@ func writeGadgetConfig() error {
|
|||
}
|
||||
err = writeGadgetAttrs(hid1Path, [][]string{
|
||||
{"protocol", "2"},
|
||||
{"subclass", "0"},
|
||||
{"subclass", "1"},
|
||||
{"report_length", "6"},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
8
web.go
8
web.go
|
@ -17,8 +17,10 @@ import (
|
|||
var staticFiles embed.FS
|
||||
|
||||
type WebRTCSessionRequest struct {
|
||||
Sd string `json:"sd"`
|
||||
OidcGoogle string `json:"OidcGoogle,omitempty"`
|
||||
Sd string `json:"sd"`
|
||||
OidcGoogle string `json:"OidcGoogle,omitempty"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
ICEServers []string `json:"iceServers,omitempty"`
|
||||
}
|
||||
|
||||
type SetPasswordRequest struct {
|
||||
|
@ -116,7 +118,7 @@ func handleWebRTCSession(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
session, err := newSession()
|
||||
session, err := newSession(SessionConfig{})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err})
|
||||
return
|
||||
|
|
33
webrtc.go
33
webrtc.go
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/pion/webrtc/v4"
|
||||
|
@ -19,6 +20,12 @@ type Session struct {
|
|||
shouldUmountVirtualMedia bool
|
||||
}
|
||||
|
||||
type SessionConfig struct {
|
||||
ICEServers []string
|
||||
LocalIP string
|
||||
IsCloud bool
|
||||
}
|
||||
|
||||
func (s *Session) ExchangeOffer(offerStr string) (string, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(offerStr)
|
||||
if err != nil {
|
||||
|
@ -61,9 +68,29 @@ func (s *Session) ExchangeOffer(offerStr string) (string, error) {
|
|||
return base64.StdEncoding.EncodeToString(localDescription), nil
|
||||
}
|
||||
|
||||
func newSession() (*Session, error) {
|
||||
peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{
|
||||
ICEServers: []webrtc.ICEServer{{}},
|
||||
func newSession(config SessionConfig) (*Session, error) {
|
||||
webrtcSettingEngine := webrtc.SettingEngine{}
|
||||
iceServer := webrtc.ICEServer{}
|
||||
|
||||
if config.IsCloud {
|
||||
if config.ICEServers == nil {
|
||||
fmt.Printf("ICE Servers not provided by cloud")
|
||||
} else {
|
||||
iceServer.URLs = config.ICEServers
|
||||
fmt.Printf("Using ICE Servers provided by cloud: %v\n", iceServer.URLs)
|
||||
}
|
||||
|
||||
if config.LocalIP == "" || net.ParseIP(config.LocalIP) == nil {
|
||||
fmt.Printf("Local IP address %v not provided or invalid, won't set NAT1To1IPs\n", config.LocalIP)
|
||||
} else {
|
||||
webrtcSettingEngine.SetNAT1To1IPs([]string{config.LocalIP}, webrtc.ICECandidateTypeSrflx)
|
||||
fmt.Printf("Setting NAT1To1IPs to %s\n", config.LocalIP)
|
||||
}
|
||||
}
|
||||
|
||||
api := webrtc.NewAPI(webrtc.WithSettingEngine(webrtcSettingEngine))
|
||||
peerConnection, err := api.NewPeerConnection(webrtc.Configuration{
|
||||
ICEServers: []webrtc.ICEServer{iceServer},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
Loading…
Reference in New Issue