diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 61900d9..71b2d58 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,6 +40,15 @@ jobs: - name: Install dependencies run: npm install + - name: Copy required files to dist + if: matrix.platform == 'windows-latest' + run: | + mkdir .\dist\ + cp .\install\windows\Dwarfium.bat .\dist\ + cp .\install\windows\mediamtx.exe .\dist\ + cp .\install\windows\mediamtx.yml .\dist\ + cp .\install\windows\Start_Dwarfium.exe .\dist\ + - name: Build if: matrix.platform == 'ubuntu-20.04' run: | diff --git a/data/mediamtx/mediamtx.yml b/data/mediamtx/mediamtx.yml new file mode 100644 index 0000000..1cf1115 --- /dev/null +++ b/data/mediamtx/mediamtx.yml @@ -0,0 +1,715 @@ +############################################### +# Global settings + +# Settings in this section are applied anywhere. + +############################################### +# Global settings -> General + +# Verbosity of the program; available values are "error", "warn", "info", "debug". +logLevel: info +# Destinations of log messages; available values are "stdout", "file" and "syslog". +logDestinations: [stdout] +# If "file" is in logDestinations, this is the file which will receive the logs. +logFile: mediamtx.log + +# Timeout of read operations. +readTimeout: 10s +# Timeout of write operations. +writeTimeout: 10s +# Size of the queue of outgoing packets. +# A higher value allows to increase throughput, a lower value allows to save RAM. +writeQueueSize: 512 +# Maximum size of outgoing UDP packets. +# This can be decreased to avoid fragmentation on networks with a low UDP MTU. +udpMaxPayloadSize: 1472 + +# Command to run when a client connects to the server. +# This is terminated with SIGINT when a client disconnects from the server. +# The following environment variables are available: +# * RTSP_PORT: RTSP server port +# * MTX_CONN_TYPE: connection type +# * MTX_CONN_ID: connection ID +runOnConnect: +# Restart the command if it exits. +runOnConnectRestart: no +# Command to run when a client disconnects from the server. +# Environment variables are the same of runOnConnect. +runOnDisconnect: + +############################################### +# Global settings -> Authentication + +# Authentication method. Available values are: +# * internal: users are stored in the configuration file +# * http: an external HTTP URL is contacted to perform authentication +# * jwt: an external identity server provides authentication through JWTs +authMethod: internal + +# Internal authentication. +# list of users. +authInternalUsers: + # Default unprivileged user. + # Username. 'any' means any user, including anonymous ones. +- user: any + # Password. Not used in case of 'any' user. + pass: + # IPs or networks allowed to use this user. An empty list means any IP. + ips: [] + # List of permissions. + permissions: + # Available actions are: publish, read, playback, api, metrics, pprof. + - action: publish + # Paths can be set to further restrict access to a specific path. + # An empty path means any path. + # Regular expressions can be used by using a tilde as prefix. + path: + - action: read + path: + - action: playback + path: + + # Default administrator. + # This allows to use API, metrics and PPROF without authentication, + # if the IP is localhost. +- user: any + pass: + ips: ['127.0.0.1', '::1'] + permissions: + - action: api + - action: metrics + - action: pprof + +# HTTP-based authentication. +# URL called to perform authentication. Every time a user wants +# to authenticate, the server calls this URL with the POST method +# and a body containing: +# { +# "user": "user", +# "password": "password", +# "ip": "ip", +# "action": "publish|read|playback|api|metrics|pprof", +# "path": "path", +# "protocol": "rtsp|rtmp|hls|webrtc|srt", +# "id": "id", +# "query": "query" +# } +# If the response code is 20x, authentication is accepted, otherwise +# it is discarded. +authHTTPAddress: +# Actions to exclude from HTTP-based authentication. +# Format is the same as the one of user permissions. +authHTTPExclude: +- action: api +- action: metrics +- action: pprof + +# JWT-based authentication. +# Users have to login through an external identity server and obtain a JWT. +# This JWT must contain the claim "mediamtx_permissions" with permissions, +# for instance: +# { +# ... +# "mediamtx_permissions": [ +# { +# "action": "publish", +# "path": "somepath" +# } +# ] +# } +# Users are expected to pass the JWT in the Authorization header or as a query parameter. +# This is the JWKS URL that will be used to pull (once) the public key that allows +# to validate JWTs. +authJWTJWKS: +# name of the claim that contains permissions. +authJWTClaimKey: mediamtx_permissions + +############################################### +# Global settings -> Control API + +# Enable controlling the server through the Control API. +api: yes +# Address of the Control API listener. +apiAddress: :9997 +# Enable TLS/HTTPS on the Control API server. +apiEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +apiServerKey: server.key +# Path to the server certificate. +apiServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +apiAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +apiTrustedProxies: [] + +############################################### +# Global settings -> Metrics + +# Enable Prometheus-compatible metrics. +metrics: no +# Address of the metrics HTTP listener. +metricsAddress: :9998 +# Enable TLS/HTTPS on the Metrics server. +metricsEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +metricsServerKey: server.key +# Path to the server certificate. +metricsServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +metricsAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +metricsTrustedProxies: [] + +############################################### +# Global settings -> PPROF + +# Enable pprof-compatible endpoint to monitor performances. +pprof: no +# Address of the pprof listener. +pprofAddress: :9999 +# Enable TLS/HTTPS on the pprof server. +pprofEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +pprofServerKey: server.key +# Path to the server certificate. +pprofServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +pprofAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +pprofTrustedProxies: [] + +############################################### +# Global settings -> Playback server + +# Enable downloading recordings from the playback server. +playback: no +# Address of the playback server listener. +playbackAddress: :9996 +# Enable TLS/HTTPS on the playback server. +playbackEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +playbackServerKey: server.key +# Path to the server certificate. +playbackServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +playbackAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +playbackTrustedProxies: [] + +############################################### +# Global settings -> RTSP server + +# Enable publishing and reading streams with the RTSP protocol. +rtsp: no +# List of enabled RTSP transport protocols. +# UDP is the most performant, but doesn't work when there's a NAT/firewall between +# server and clients, and doesn't support encryption. +# UDP-multicast allows to save bandwidth when clients are all in the same LAN. +# TCP is the most versatile, and does support encryption. +# The handshake is always performed with TCP. +protocols: [udp, multicast, tcp] +# Encrypt handshakes and TCP streams with TLS (RTSPS). +# Available values are "no", "strict", "optional". +encryption: "no" +# Address of the TCP/RTSP listener. This is needed only when encryption is "no" or "optional". +rtspAddress: :8554 +# Address of the TCP/TLS/RTSPS listener. This is needed only when encryption is "strict" or "optional". +rtspsAddress: :8322 +# Address of the UDP/RTP listener. This is needed only when "udp" is in protocols. +rtpAddress: :8000 +# Address of the UDP/RTCP listener. This is needed only when "udp" is in protocols. +rtcpAddress: :8001 +# IP range of all UDP-multicast listeners. This is needed only when "multicast" is in protocols. +multicastIPRange: 224.1.0.0/16 +# Port of all UDP-multicast/RTP listeners. This is needed only when "multicast" is in protocols. +multicastRTPPort: 8002 +# Port of all UDP-multicast/RTCP listeners. This is needed only when "multicast" is in protocols. +multicastRTCPPort: 8003 +# Path to the server key. This is needed only when encryption is "strict" or "optional". +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +serverKey: server.key +# Path to the server certificate. This is needed only when encryption is "strict" or "optional". +serverCert: server.crt +# Authentication methods. Available are "basic" and "digest". +# "digest" doesn't provide any additional security and is available for compatibility only. +rtspAuthMethods: [basic] + +############################################### +# Global settings -> RTMP server + +# Enable publishing and reading streams with the RTMP protocol. +rtmp: no +# Address of the RTMP listener. This is needed only when encryption is "no" or "optional". +rtmpAddress: :1935 +# Encrypt connections with TLS (RTMPS). +# Available values are "no", "strict", "optional". +rtmpEncryption: "no" +# Address of the RTMPS listener. This is needed only when encryption is "strict" or "optional". +rtmpsAddress: :1936 +# Path to the server key. This is needed only when encryption is "strict" or "optional". +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +rtmpServerKey: server.key +# Path to the server certificate. This is needed only when encryption is "strict" or "optional". +rtmpServerCert: server.crt + +############################################### +# Global settings -> HLS server + +# Enable reading streams with the HLS protocol. +hls: yes +# Address of the HLS listener. +hlsAddress: :8888 +# Enable TLS/HTTPS on the HLS server. +# This is required for Low-Latency HLS. +hlsEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +hlsServerKey: server.key +# Path to the server certificate. +hlsServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +# This allows to play the HLS stream from an external website. +hlsAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HLS server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +hlsTrustedProxies: [] +# By default, HLS is generated only when requested by a user. +# This option allows to generate it always, avoiding the delay between request and generation. +hlsAlwaysRemux: no +# Variant of the HLS protocol to use. Available options are: +# * mpegts - uses MPEG-TS segments, for maximum compatibility. +# * fmp4 - uses fragmented MP4 segments, more efficient. +# * lowLatency - uses Low-Latency HLS. +hlsVariant: lowLatency +# Number of HLS segments to keep on the server. +# Segments allow to seek through the stream. +# Their number doesn't influence latency. +hlsSegmentCount: 7 +# Minimum duration of each segment. +# A player usually puts 3 segments in a buffer before reproducing the stream. +# The final segment duration is also influenced by the interval between IDR frames, +# since the server changes the duration in order to include at least one IDR frame +# in each segment. +hlsSegmentDuration: 500ms +# Minimum duration of each part. +# A player usually puts 3 parts in a buffer before reproducing the stream. +# Parts are used in Low-Latency HLS in place of segments. +# Part duration is influenced by the distance between video/audio samples +# and is adjusted in order to produce segments with a similar duration. +hlsPartDuration: 150ms +# Maximum size of each segment. +# This prevents RAM exhaustion. +hlsSegmentMaxSize: 50M +# Directory in which to save segments, instead of keeping them in the RAM. +# This decreases performance, since reading from disk is less performant than +# reading from RAM, but allows to save RAM. +hlsDirectory: '' +# The muxer will be closed when there are no +# reader requests and this amount of time has passed. +hlsMuxerCloseAfter: 60s + +############################################### +# Global settings -> WebRTC server + +# Enable publishing and reading streams with the WebRTC protocol. +webrtc: no +# Address of the WebRTC HTTP listener. +webrtcAddress: :8889 +# Enable TLS/HTTPS on the WebRTC server. +webrtcEncryption: no +# Path to the server key. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +webrtcServerKey: server.key +# Path to the server certificate. +webrtcServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +# This allows to play the WebRTC stream from an external website. +webrtcAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the WebRTC server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +webrtcTrustedProxies: [] +# Address of a local UDP listener that will receive connections. +# Use a blank string to disable. +webrtcLocalUDPAddress: :8189 +# Address of a local TCP listener that will receive connections. +# This is disabled by default since TCP is less efficient than UDP and +# introduces a progressive delay when network is congested. +webrtcLocalTCPAddress: '' +# WebRTC clients need to know the IP of the server. +# Gather IPs from interfaces and send them to clients. +webrtcIPsFromInterfaces: yes +# List of interfaces whose IPs will be sent to clients. +# An empty value means to use all available interfaces. +webrtcIPsFromInterfacesList: [] +# List of additional hosts or IPs to send to clients. +webrtcAdditionalHosts: [] +# ICE servers. Needed only when local listeners can't be reached by clients. +# STUN servers allows to obtain and share the public IP of the server. +# TURN/TURNS servers forces all traffic through them. +webrtcICEServers2: [] + # - url: stun:stun.l.google.com:19302 + # if user is "AUTH_SECRET", then authentication is secret based. + # the secret must be inserted into the password field. + # username: '' + # password: '' + # clientOnly: false +# Time to wait for the WebRTC handshake to complete. +webrtcHandshakeTimeout: 10s +# Maximum time to gather video tracks. +webrtcTrackGatherTimeout: 2s + +############################################### +# Global settings -> SRT server + +# Enable publishing and reading streams with the SRT protocol. +srt: no +# Address of the SRT listener. +srtAddress: :8890 + +############################################### +# Default path settings + +# Settings in "pathDefaults" are applied anywhere, +# unless they are overridden in "paths". +pathDefaults: + + ############################################### + # Default path settings -> General + + # Source of the stream. This can be: + # * publisher -> the stream is provided by a RTSP, RTMP, WebRTC or SRT client + # * rtsp://existing-url -> the stream is pulled from another RTSP server / camera + # * rtsps://existing-url -> the stream is pulled from another RTSP server / camera with RTSPS + # * rtmp://existing-url -> the stream is pulled from another RTMP server / camera + # * rtmps://existing-url -> the stream is pulled from another RTMP server / camera with RTMPS + # * http://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera + # * https://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera with HTTPS + # * udp://ip:port -> the stream is pulled with UDP, by listening on the specified IP and port + # * srt://existing-url -> the stream is pulled from another SRT server / camera + # * whep://existing-url -> the stream is pulled from another WebRTC server / camera + # * wheps://existing-url -> the stream is pulled from another WebRTC server / camera with HTTPS + # * redirect -> the stream is provided by another path or server + # * rpiCamera -> the stream is provided by a Raspberry Pi Camera + # The following variables can be used in the source string: + # * $MTX_QUERY: query parameters (passed by first reader) + # * $G1, $G2, ...: regular expression groups, if path name is + # a regular expression. + source: publisher + # If the source is a URL, and the source certificate is self-signed + # or invalid, you can provide the fingerprint of the certificate in order to + # validate it anyway. It can be obtained by running: + # openssl s_client -connect source_ip:source_port /dev/null | sed -n '/BEGIN/,/END/p' > server.crt + # openssl x509 -in server.crt -noout -fingerprint -sha256 | cut -d "=" -f2 | tr -d ':' + sourceFingerprint: + # If the source is a URL, it will be pulled only when at least + # one reader is connected, saving bandwidth. + sourceOnDemand: no + # If sourceOnDemand is "yes", readers will be put on hold until the source is + # ready or until this amount of time has passed. + sourceOnDemandStartTimeout: 10s + # If sourceOnDemand is "yes", the source will be closed when there are no + # readers connected and this amount of time has passed. + sourceOnDemandCloseAfter: 10s + # Maximum number of readers. Zero means no limit. + maxReaders: 0 + # SRT encryption passphrase require to read from this path + srtReadPassphrase: + # If the stream is not available, redirect readers to this path. + # It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL. + fallback: + + ############################################### + # Default path settings -> Record + + # Record streams to disk. + record: yes + # Path of recording segments. + # Extension is added automatically. + # Available variables are %path (path name), %Y %m %d %H %M %S %f %s (time in strftime format) + recordPath: ./recordings/%path/%Y-%m-%d_%H-%M-%S-%f + # Format of recorded segments. + # Available formats are "fmp4" (fragmented MP4) and "mpegts" (MPEG-TS). + recordFormat: fmp4 + # fMP4 segments are concatenation of small MP4 files (parts), each with this duration. + # MPEG-TS segments are concatenation of 188-bytes packets, flushed to disk with this period. + # When a system failure occurs, the last part gets lost. + # Therefore, the part duration is equal to the RPO (recovery point objective). + recordPartDuration: 1s + # Minimum duration of each segment. + recordSegmentDuration: 1h + # Delete segments after this timespan. + # Set to 0s to disable automatic deletion. + recordDeleteAfter: 24h + + ############################################### + # Default path settings -> Publisher source (when source is "publisher") + + # Allow another client to disconnect the current publisher and publish in its place. + overridePublisher: yes + # SRT encryption passphrase required to publish to this path + srtPublishPassphrase: + + ############################################### + # Default path settings -> RTSP source (when source is a RTSP or a RTSPS URL) + + # Transport protocol used to pull the stream. available values are "automatic", "udp", "multicast", "tcp". + rtspTransport: automatic + # Support sources that don't provide server ports or use random server ports. This is a security issue + # and must be used only when interacting with sources that require it. + rtspAnyPort: no + # Range header to send to the source, in order to start streaming from the specified offset. + # available values: + # * clock: Absolute time + # * npt: Normal Play Time + # * smpte: SMPTE timestamps relative to the start of the recording + rtspRangeType: + # Available values: + # * clock: UTC ISO 8601 combined date and time string, e.g. 20230812T120000Z + # * npt: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h" + # * smpte: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h" + rtspRangeStart: + + ############################################### + # Default path settings -> Redirect source (when source is "redirect") + + # RTSP URL which clients will be redirected to. + sourceRedirect: + + ############################################### + # Default path settings -> Raspberry Pi Camera source (when source is "rpiCamera") + + # ID of the camera + rpiCameraCamID: 0 + # Width of frames + rpiCameraWidth: 1920 + # Height of frames + rpiCameraHeight: 1080 + # Flip horizontally + rpiCameraHFlip: false + # Flip vertically + rpiCameraVFlip: false + # Brightness [-1, 1] + rpiCameraBrightness: 0 + # Contrast [0, 16] + rpiCameraContrast: 1 + # Saturation [0, 16] + rpiCameraSaturation: 1 + # Sharpness [0, 16] + rpiCameraSharpness: 1 + # Exposure mode. + # values: normal, short, long, custom + rpiCameraExposure: normal + # Auto-white-balance mode. + # values: auto, incandescent, tungsten, fluorescent, indoor, daylight, cloudy, custom + rpiCameraAWB: auto + # Auto-white-balance fixed gains. This can be used in place of rpiCameraAWB. + # format: [red,blue] + rpiCameraAWBGains: [0, 0] + # Denoise operating mode. + # values: off, cdn_off, cdn_fast, cdn_hq + rpiCameraDenoise: "off" + # Fixed shutter speed, in microseconds. + rpiCameraShutter: 0 + # Metering mode of the AEC/AGC algorithm. + # values: centre, spot, matrix, custom + rpiCameraMetering: centre + # Fixed gain + rpiCameraGain: 0 + # EV compensation of the image [-10, 10] + rpiCameraEV: 0 + # Region of interest, in format x,y,width,height + rpiCameraROI: + # Whether to enable HDR on Raspberry Camera 3. + rpiCameraHDR: false + # Tuning file + rpiCameraTuningFile: + # Sensor mode, in format [width]:[height]:[bit-depth]:[packing] + # bit-depth and packing are optional. + rpiCameraMode: + # frames per second + rpiCameraFPS: 30 + # Autofocus mode + # values: auto, manual, continuous + rpiCameraAfMode: continuous + # Autofocus range + # values: normal, macro, full + rpiCameraAfRange: normal + # Autofocus speed + # values: normal, fast + rpiCameraAfSpeed: normal + # Lens position (for manual autofocus only), will be set to focus to a specific distance + # calculated by the following formula: d = 1 / value + # Examples: 0 moves the lens to infinity. + # 0.5 moves the lens to focus on objects 2m away. + # 2 moves the lens to focus on objects 50cm away. + rpiCameraLensPosition: 0.0 + # Specifies the autofocus window, in the form x,y,width,height where the coordinates + # are given as a proportion of the entire image. + rpiCameraAfWindow: + # Manual flicker correction period, in microseconds. + rpiCameraFlickerPeriod: 0 + # Enables printing text on each frame. + rpiCameraTextOverlayEnable: false + # Text that is printed on each frame. + # format is the one of the strftime() function. + rpiCameraTextOverlay: '%Y-%m-%d %H:%M:%S - MediaMTX' + # Codec. Available values: auto, hardwareH264, softwareH264 + rpiCameraCodec: auto + # Period between IDR frames + rpiCameraIDRPeriod: 60 + # Bitrate + rpiCameraBitrate: 1000000 + # H264 profile + rpiCameraProfile: main + # H264 level + rpiCameraLevel: '4.1' + + ############################################### + # Default path settings -> Hooks + + # Command to run when this path is initialized. + # This can be used to publish a stream when the server is launched. + # This is terminated with SIGINT when the program closes. + # The following environment variables are available: + # * MTX_PATH: path name + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnInit: + # Restart the command if it exits. + runOnInitRestart: no + + # Command to run when this path is requested by a reader + # and no one is publishing to this path yet. + # This can be used to publish a stream on demand. + # This is terminated with SIGINT when there are no readers anymore. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_QUERY: query parameters (passed by first reader) + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnDemand: + # Restart the command if it exits. + runOnDemandRestart: no + # Readers will be put on hold until the runOnDemand command starts publishing + # or until this amount of time has passed. + runOnDemandStartTimeout: 10s + # The command will be closed when there are no + # readers connected and this amount of time has passed. + runOnDemandCloseAfter: 10s + # Command to run when there are no readers anymore. + # Environment variables are the same of runOnDemand. + runOnUnDemand: + + # Command to run when the stream is ready to be read, whenever it is + # published by a client or pulled from a server / camera. + # This is terminated with SIGINT when the stream is not ready anymore. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_QUERY: query parameters (passed by publisher) + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + # * MTX_SOURCE_TYPE: source type + # * MTX_SOURCE_ID: source ID + runOnReady: + # Restart the command if it exits. + runOnReadyRestart: no + # Command to run when the stream is not available anymore. + # Environment variables are the same of runOnReady. + runOnNotReady: + + # Command to run when a client starts reading. + # This is terminated with SIGINT when a client stops reading. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_QUERY: query parameters (passed by reader) + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + # * MTX_READER_TYPE: reader type + # * MTX_READER_ID: reader ID + runOnRead: + # Restart the command if it exits. + runOnReadRestart: no + # Command to run when a client stops reading. + # Environment variables are the same of runOnRead. + runOnUnread: + + # Command to run when a recording segment is created. + # The following environment variables are available: + # * MTX_PATH: path name + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + # * MTX_SEGMENT_PATH: segment file path + runOnRecordSegmentCreate: + + # Command to run when a recording segment is complete. + # The following environment variables are available: + # * MTX_PATH: path name + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + # * MTX_SEGMENT_PATH: segment file path + # * MTX_SEGMENT_DURATION: segment duration + runOnRecordSegmentComplete: + +############################################### +# Path settings + +# Settings in "paths" are applied to specific paths, and the map key +# is the name of the path. +# Any setting in "pathDefaults" can be overridden here. +# It's possible to use regular expressions by using a tilde as prefix, +# for example "~^(test1|test2)$" will match both "test1" and "test2", +# for example "~^prefix" will match all paths that start with "prefix". +paths: + # example: + # my_camera: + # source: rtsp://my_camera + dwarf_wide: + source: rtsp://192.168.0.252:554/ch1/stream0 + sourceOnDemand: yes + sourceOnDemandCloseAfter: 10s + record: no + dwarf_tele: + source: rtsp://192.168.0.252:554/ch0/stream0 + sourceOnDemand: yes + sourceOnDemandCloseAfter: 10s + record: no + + # Settings under path "all_others" are applied to all paths that + # do not match another entry. + all_others: diff --git a/install/windows/Start_Dwarfium.exe b/install/windows/Start_Dwarfium.exe new file mode 100644 index 0000000..b1168a3 Binary files /dev/null and b/install/windows/Start_Dwarfium.exe differ diff --git a/install/windows/dwarfium.bat b/install/windows/dwarfium.bat new file mode 100644 index 0000000..4795b71 --- /dev/null +++ b/install/windows/dwarfium.bat @@ -0,0 +1,6 @@ +@echo off +REM Launch Dwarfium application +start "" "%~dp0\Dwarfium.exe" + +REM Launch the mediamtx application +start /Min "Dwarfium Video Server" "%~dp0\mediamtx.exe" diff --git a/install/windows/mediamtx.exe b/install/windows/mediamtx.exe new file mode 100644 index 0000000..ba6a7e4 Binary files /dev/null and b/install/windows/mediamtx.exe differ diff --git a/install/windows/mediamtx.yml b/install/windows/mediamtx.yml new file mode 100644 index 0000000..1cf1115 --- /dev/null +++ b/install/windows/mediamtx.yml @@ -0,0 +1,715 @@ +############################################### +# Global settings + +# Settings in this section are applied anywhere. + +############################################### +# Global settings -> General + +# Verbosity of the program; available values are "error", "warn", "info", "debug". +logLevel: info +# Destinations of log messages; available values are "stdout", "file" and "syslog". +logDestinations: [stdout] +# If "file" is in logDestinations, this is the file which will receive the logs. +logFile: mediamtx.log + +# Timeout of read operations. +readTimeout: 10s +# Timeout of write operations. +writeTimeout: 10s +# Size of the queue of outgoing packets. +# A higher value allows to increase throughput, a lower value allows to save RAM. +writeQueueSize: 512 +# Maximum size of outgoing UDP packets. +# This can be decreased to avoid fragmentation on networks with a low UDP MTU. +udpMaxPayloadSize: 1472 + +# Command to run when a client connects to the server. +# This is terminated with SIGINT when a client disconnects from the server. +# The following environment variables are available: +# * RTSP_PORT: RTSP server port +# * MTX_CONN_TYPE: connection type +# * MTX_CONN_ID: connection ID +runOnConnect: +# Restart the command if it exits. +runOnConnectRestart: no +# Command to run when a client disconnects from the server. +# Environment variables are the same of runOnConnect. +runOnDisconnect: + +############################################### +# Global settings -> Authentication + +# Authentication method. Available values are: +# * internal: users are stored in the configuration file +# * http: an external HTTP URL is contacted to perform authentication +# * jwt: an external identity server provides authentication through JWTs +authMethod: internal + +# Internal authentication. +# list of users. +authInternalUsers: + # Default unprivileged user. + # Username. 'any' means any user, including anonymous ones. +- user: any + # Password. Not used in case of 'any' user. + pass: + # IPs or networks allowed to use this user. An empty list means any IP. + ips: [] + # List of permissions. + permissions: + # Available actions are: publish, read, playback, api, metrics, pprof. + - action: publish + # Paths can be set to further restrict access to a specific path. + # An empty path means any path. + # Regular expressions can be used by using a tilde as prefix. + path: + - action: read + path: + - action: playback + path: + + # Default administrator. + # This allows to use API, metrics and PPROF without authentication, + # if the IP is localhost. +- user: any + pass: + ips: ['127.0.0.1', '::1'] + permissions: + - action: api + - action: metrics + - action: pprof + +# HTTP-based authentication. +# URL called to perform authentication. Every time a user wants +# to authenticate, the server calls this URL with the POST method +# and a body containing: +# { +# "user": "user", +# "password": "password", +# "ip": "ip", +# "action": "publish|read|playback|api|metrics|pprof", +# "path": "path", +# "protocol": "rtsp|rtmp|hls|webrtc|srt", +# "id": "id", +# "query": "query" +# } +# If the response code is 20x, authentication is accepted, otherwise +# it is discarded. +authHTTPAddress: +# Actions to exclude from HTTP-based authentication. +# Format is the same as the one of user permissions. +authHTTPExclude: +- action: api +- action: metrics +- action: pprof + +# JWT-based authentication. +# Users have to login through an external identity server and obtain a JWT. +# This JWT must contain the claim "mediamtx_permissions" with permissions, +# for instance: +# { +# ... +# "mediamtx_permissions": [ +# { +# "action": "publish", +# "path": "somepath" +# } +# ] +# } +# Users are expected to pass the JWT in the Authorization header or as a query parameter. +# This is the JWKS URL that will be used to pull (once) the public key that allows +# to validate JWTs. +authJWTJWKS: +# name of the claim that contains permissions. +authJWTClaimKey: mediamtx_permissions + +############################################### +# Global settings -> Control API + +# Enable controlling the server through the Control API. +api: yes +# Address of the Control API listener. +apiAddress: :9997 +# Enable TLS/HTTPS on the Control API server. +apiEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +apiServerKey: server.key +# Path to the server certificate. +apiServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +apiAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +apiTrustedProxies: [] + +############################################### +# Global settings -> Metrics + +# Enable Prometheus-compatible metrics. +metrics: no +# Address of the metrics HTTP listener. +metricsAddress: :9998 +# Enable TLS/HTTPS on the Metrics server. +metricsEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +metricsServerKey: server.key +# Path to the server certificate. +metricsServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +metricsAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +metricsTrustedProxies: [] + +############################################### +# Global settings -> PPROF + +# Enable pprof-compatible endpoint to monitor performances. +pprof: no +# Address of the pprof listener. +pprofAddress: :9999 +# Enable TLS/HTTPS on the pprof server. +pprofEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +pprofServerKey: server.key +# Path to the server certificate. +pprofServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +pprofAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +pprofTrustedProxies: [] + +############################################### +# Global settings -> Playback server + +# Enable downloading recordings from the playback server. +playback: no +# Address of the playback server listener. +playbackAddress: :9996 +# Enable TLS/HTTPS on the playback server. +playbackEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +playbackServerKey: server.key +# Path to the server certificate. +playbackServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +playbackAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +playbackTrustedProxies: [] + +############################################### +# Global settings -> RTSP server + +# Enable publishing and reading streams with the RTSP protocol. +rtsp: no +# List of enabled RTSP transport protocols. +# UDP is the most performant, but doesn't work when there's a NAT/firewall between +# server and clients, and doesn't support encryption. +# UDP-multicast allows to save bandwidth when clients are all in the same LAN. +# TCP is the most versatile, and does support encryption. +# The handshake is always performed with TCP. +protocols: [udp, multicast, tcp] +# Encrypt handshakes and TCP streams with TLS (RTSPS). +# Available values are "no", "strict", "optional". +encryption: "no" +# Address of the TCP/RTSP listener. This is needed only when encryption is "no" or "optional". +rtspAddress: :8554 +# Address of the TCP/TLS/RTSPS listener. This is needed only when encryption is "strict" or "optional". +rtspsAddress: :8322 +# Address of the UDP/RTP listener. This is needed only when "udp" is in protocols. +rtpAddress: :8000 +# Address of the UDP/RTCP listener. This is needed only when "udp" is in protocols. +rtcpAddress: :8001 +# IP range of all UDP-multicast listeners. This is needed only when "multicast" is in protocols. +multicastIPRange: 224.1.0.0/16 +# Port of all UDP-multicast/RTP listeners. This is needed only when "multicast" is in protocols. +multicastRTPPort: 8002 +# Port of all UDP-multicast/RTCP listeners. This is needed only when "multicast" is in protocols. +multicastRTCPPort: 8003 +# Path to the server key. This is needed only when encryption is "strict" or "optional". +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +serverKey: server.key +# Path to the server certificate. This is needed only when encryption is "strict" or "optional". +serverCert: server.crt +# Authentication methods. Available are "basic" and "digest". +# "digest" doesn't provide any additional security and is available for compatibility only. +rtspAuthMethods: [basic] + +############################################### +# Global settings -> RTMP server + +# Enable publishing and reading streams with the RTMP protocol. +rtmp: no +# Address of the RTMP listener. This is needed only when encryption is "no" or "optional". +rtmpAddress: :1935 +# Encrypt connections with TLS (RTMPS). +# Available values are "no", "strict", "optional". +rtmpEncryption: "no" +# Address of the RTMPS listener. This is needed only when encryption is "strict" or "optional". +rtmpsAddress: :1936 +# Path to the server key. This is needed only when encryption is "strict" or "optional". +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +rtmpServerKey: server.key +# Path to the server certificate. This is needed only when encryption is "strict" or "optional". +rtmpServerCert: server.crt + +############################################### +# Global settings -> HLS server + +# Enable reading streams with the HLS protocol. +hls: yes +# Address of the HLS listener. +hlsAddress: :8888 +# Enable TLS/HTTPS on the HLS server. +# This is required for Low-Latency HLS. +hlsEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +hlsServerKey: server.key +# Path to the server certificate. +hlsServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +# This allows to play the HLS stream from an external website. +hlsAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HLS server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +hlsTrustedProxies: [] +# By default, HLS is generated only when requested by a user. +# This option allows to generate it always, avoiding the delay between request and generation. +hlsAlwaysRemux: no +# Variant of the HLS protocol to use. Available options are: +# * mpegts - uses MPEG-TS segments, for maximum compatibility. +# * fmp4 - uses fragmented MP4 segments, more efficient. +# * lowLatency - uses Low-Latency HLS. +hlsVariant: lowLatency +# Number of HLS segments to keep on the server. +# Segments allow to seek through the stream. +# Their number doesn't influence latency. +hlsSegmentCount: 7 +# Minimum duration of each segment. +# A player usually puts 3 segments in a buffer before reproducing the stream. +# The final segment duration is also influenced by the interval between IDR frames, +# since the server changes the duration in order to include at least one IDR frame +# in each segment. +hlsSegmentDuration: 500ms +# Minimum duration of each part. +# A player usually puts 3 parts in a buffer before reproducing the stream. +# Parts are used in Low-Latency HLS in place of segments. +# Part duration is influenced by the distance between video/audio samples +# and is adjusted in order to produce segments with a similar duration. +hlsPartDuration: 150ms +# Maximum size of each segment. +# This prevents RAM exhaustion. +hlsSegmentMaxSize: 50M +# Directory in which to save segments, instead of keeping them in the RAM. +# This decreases performance, since reading from disk is less performant than +# reading from RAM, but allows to save RAM. +hlsDirectory: '' +# The muxer will be closed when there are no +# reader requests and this amount of time has passed. +hlsMuxerCloseAfter: 60s + +############################################### +# Global settings -> WebRTC server + +# Enable publishing and reading streams with the WebRTC protocol. +webrtc: no +# Address of the WebRTC HTTP listener. +webrtcAddress: :8889 +# Enable TLS/HTTPS on the WebRTC server. +webrtcEncryption: no +# Path to the server key. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +webrtcServerKey: server.key +# Path to the server certificate. +webrtcServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +# This allows to play the WebRTC stream from an external website. +webrtcAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the WebRTC server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +webrtcTrustedProxies: [] +# Address of a local UDP listener that will receive connections. +# Use a blank string to disable. +webrtcLocalUDPAddress: :8189 +# Address of a local TCP listener that will receive connections. +# This is disabled by default since TCP is less efficient than UDP and +# introduces a progressive delay when network is congested. +webrtcLocalTCPAddress: '' +# WebRTC clients need to know the IP of the server. +# Gather IPs from interfaces and send them to clients. +webrtcIPsFromInterfaces: yes +# List of interfaces whose IPs will be sent to clients. +# An empty value means to use all available interfaces. +webrtcIPsFromInterfacesList: [] +# List of additional hosts or IPs to send to clients. +webrtcAdditionalHosts: [] +# ICE servers. Needed only when local listeners can't be reached by clients. +# STUN servers allows to obtain and share the public IP of the server. +# TURN/TURNS servers forces all traffic through them. +webrtcICEServers2: [] + # - url: stun:stun.l.google.com:19302 + # if user is "AUTH_SECRET", then authentication is secret based. + # the secret must be inserted into the password field. + # username: '' + # password: '' + # clientOnly: false +# Time to wait for the WebRTC handshake to complete. +webrtcHandshakeTimeout: 10s +# Maximum time to gather video tracks. +webrtcTrackGatherTimeout: 2s + +############################################### +# Global settings -> SRT server + +# Enable publishing and reading streams with the SRT protocol. +srt: no +# Address of the SRT listener. +srtAddress: :8890 + +############################################### +# Default path settings + +# Settings in "pathDefaults" are applied anywhere, +# unless they are overridden in "paths". +pathDefaults: + + ############################################### + # Default path settings -> General + + # Source of the stream. This can be: + # * publisher -> the stream is provided by a RTSP, RTMP, WebRTC or SRT client + # * rtsp://existing-url -> the stream is pulled from another RTSP server / camera + # * rtsps://existing-url -> the stream is pulled from another RTSP server / camera with RTSPS + # * rtmp://existing-url -> the stream is pulled from another RTMP server / camera + # * rtmps://existing-url -> the stream is pulled from another RTMP server / camera with RTMPS + # * http://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera + # * https://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera with HTTPS + # * udp://ip:port -> the stream is pulled with UDP, by listening on the specified IP and port + # * srt://existing-url -> the stream is pulled from another SRT server / camera + # * whep://existing-url -> the stream is pulled from another WebRTC server / camera + # * wheps://existing-url -> the stream is pulled from another WebRTC server / camera with HTTPS + # * redirect -> the stream is provided by another path or server + # * rpiCamera -> the stream is provided by a Raspberry Pi Camera + # The following variables can be used in the source string: + # * $MTX_QUERY: query parameters (passed by first reader) + # * $G1, $G2, ...: regular expression groups, if path name is + # a regular expression. + source: publisher + # If the source is a URL, and the source certificate is self-signed + # or invalid, you can provide the fingerprint of the certificate in order to + # validate it anyway. It can be obtained by running: + # openssl s_client -connect source_ip:source_port /dev/null | sed -n '/BEGIN/,/END/p' > server.crt + # openssl x509 -in server.crt -noout -fingerprint -sha256 | cut -d "=" -f2 | tr -d ':' + sourceFingerprint: + # If the source is a URL, it will be pulled only when at least + # one reader is connected, saving bandwidth. + sourceOnDemand: no + # If sourceOnDemand is "yes", readers will be put on hold until the source is + # ready or until this amount of time has passed. + sourceOnDemandStartTimeout: 10s + # If sourceOnDemand is "yes", the source will be closed when there are no + # readers connected and this amount of time has passed. + sourceOnDemandCloseAfter: 10s + # Maximum number of readers. Zero means no limit. + maxReaders: 0 + # SRT encryption passphrase require to read from this path + srtReadPassphrase: + # If the stream is not available, redirect readers to this path. + # It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL. + fallback: + + ############################################### + # Default path settings -> Record + + # Record streams to disk. + record: yes + # Path of recording segments. + # Extension is added automatically. + # Available variables are %path (path name), %Y %m %d %H %M %S %f %s (time in strftime format) + recordPath: ./recordings/%path/%Y-%m-%d_%H-%M-%S-%f + # Format of recorded segments. + # Available formats are "fmp4" (fragmented MP4) and "mpegts" (MPEG-TS). + recordFormat: fmp4 + # fMP4 segments are concatenation of small MP4 files (parts), each with this duration. + # MPEG-TS segments are concatenation of 188-bytes packets, flushed to disk with this period. + # When a system failure occurs, the last part gets lost. + # Therefore, the part duration is equal to the RPO (recovery point objective). + recordPartDuration: 1s + # Minimum duration of each segment. + recordSegmentDuration: 1h + # Delete segments after this timespan. + # Set to 0s to disable automatic deletion. + recordDeleteAfter: 24h + + ############################################### + # Default path settings -> Publisher source (when source is "publisher") + + # Allow another client to disconnect the current publisher and publish in its place. + overridePublisher: yes + # SRT encryption passphrase required to publish to this path + srtPublishPassphrase: + + ############################################### + # Default path settings -> RTSP source (when source is a RTSP or a RTSPS URL) + + # Transport protocol used to pull the stream. available values are "automatic", "udp", "multicast", "tcp". + rtspTransport: automatic + # Support sources that don't provide server ports or use random server ports. This is a security issue + # and must be used only when interacting with sources that require it. + rtspAnyPort: no + # Range header to send to the source, in order to start streaming from the specified offset. + # available values: + # * clock: Absolute time + # * npt: Normal Play Time + # * smpte: SMPTE timestamps relative to the start of the recording + rtspRangeType: + # Available values: + # * clock: UTC ISO 8601 combined date and time string, e.g. 20230812T120000Z + # * npt: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h" + # * smpte: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h" + rtspRangeStart: + + ############################################### + # Default path settings -> Redirect source (when source is "redirect") + + # RTSP URL which clients will be redirected to. + sourceRedirect: + + ############################################### + # Default path settings -> Raspberry Pi Camera source (when source is "rpiCamera") + + # ID of the camera + rpiCameraCamID: 0 + # Width of frames + rpiCameraWidth: 1920 + # Height of frames + rpiCameraHeight: 1080 + # Flip horizontally + rpiCameraHFlip: false + # Flip vertically + rpiCameraVFlip: false + # Brightness [-1, 1] + rpiCameraBrightness: 0 + # Contrast [0, 16] + rpiCameraContrast: 1 + # Saturation [0, 16] + rpiCameraSaturation: 1 + # Sharpness [0, 16] + rpiCameraSharpness: 1 + # Exposure mode. + # values: normal, short, long, custom + rpiCameraExposure: normal + # Auto-white-balance mode. + # values: auto, incandescent, tungsten, fluorescent, indoor, daylight, cloudy, custom + rpiCameraAWB: auto + # Auto-white-balance fixed gains. This can be used in place of rpiCameraAWB. + # format: [red,blue] + rpiCameraAWBGains: [0, 0] + # Denoise operating mode. + # values: off, cdn_off, cdn_fast, cdn_hq + rpiCameraDenoise: "off" + # Fixed shutter speed, in microseconds. + rpiCameraShutter: 0 + # Metering mode of the AEC/AGC algorithm. + # values: centre, spot, matrix, custom + rpiCameraMetering: centre + # Fixed gain + rpiCameraGain: 0 + # EV compensation of the image [-10, 10] + rpiCameraEV: 0 + # Region of interest, in format x,y,width,height + rpiCameraROI: + # Whether to enable HDR on Raspberry Camera 3. + rpiCameraHDR: false + # Tuning file + rpiCameraTuningFile: + # Sensor mode, in format [width]:[height]:[bit-depth]:[packing] + # bit-depth and packing are optional. + rpiCameraMode: + # frames per second + rpiCameraFPS: 30 + # Autofocus mode + # values: auto, manual, continuous + rpiCameraAfMode: continuous + # Autofocus range + # values: normal, macro, full + rpiCameraAfRange: normal + # Autofocus speed + # values: normal, fast + rpiCameraAfSpeed: normal + # Lens position (for manual autofocus only), will be set to focus to a specific distance + # calculated by the following formula: d = 1 / value + # Examples: 0 moves the lens to infinity. + # 0.5 moves the lens to focus on objects 2m away. + # 2 moves the lens to focus on objects 50cm away. + rpiCameraLensPosition: 0.0 + # Specifies the autofocus window, in the form x,y,width,height where the coordinates + # are given as a proportion of the entire image. + rpiCameraAfWindow: + # Manual flicker correction period, in microseconds. + rpiCameraFlickerPeriod: 0 + # Enables printing text on each frame. + rpiCameraTextOverlayEnable: false + # Text that is printed on each frame. + # format is the one of the strftime() function. + rpiCameraTextOverlay: '%Y-%m-%d %H:%M:%S - MediaMTX' + # Codec. Available values: auto, hardwareH264, softwareH264 + rpiCameraCodec: auto + # Period between IDR frames + rpiCameraIDRPeriod: 60 + # Bitrate + rpiCameraBitrate: 1000000 + # H264 profile + rpiCameraProfile: main + # H264 level + rpiCameraLevel: '4.1' + + ############################################### + # Default path settings -> Hooks + + # Command to run when this path is initialized. + # This can be used to publish a stream when the server is launched. + # This is terminated with SIGINT when the program closes. + # The following environment variables are available: + # * MTX_PATH: path name + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnInit: + # Restart the command if it exits. + runOnInitRestart: no + + # Command to run when this path is requested by a reader + # and no one is publishing to this path yet. + # This can be used to publish a stream on demand. + # This is terminated with SIGINT when there are no readers anymore. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_QUERY: query parameters (passed by first reader) + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnDemand: + # Restart the command if it exits. + runOnDemandRestart: no + # Readers will be put on hold until the runOnDemand command starts publishing + # or until this amount of time has passed. + runOnDemandStartTimeout: 10s + # The command will be closed when there are no + # readers connected and this amount of time has passed. + runOnDemandCloseAfter: 10s + # Command to run when there are no readers anymore. + # Environment variables are the same of runOnDemand. + runOnUnDemand: + + # Command to run when the stream is ready to be read, whenever it is + # published by a client or pulled from a server / camera. + # This is terminated with SIGINT when the stream is not ready anymore. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_QUERY: query parameters (passed by publisher) + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + # * MTX_SOURCE_TYPE: source type + # * MTX_SOURCE_ID: source ID + runOnReady: + # Restart the command if it exits. + runOnReadyRestart: no + # Command to run when the stream is not available anymore. + # Environment variables are the same of runOnReady. + runOnNotReady: + + # Command to run when a client starts reading. + # This is terminated with SIGINT when a client stops reading. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_QUERY: query parameters (passed by reader) + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + # * MTX_READER_TYPE: reader type + # * MTX_READER_ID: reader ID + runOnRead: + # Restart the command if it exits. + runOnReadRestart: no + # Command to run when a client stops reading. + # Environment variables are the same of runOnRead. + runOnUnread: + + # Command to run when a recording segment is created. + # The following environment variables are available: + # * MTX_PATH: path name + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + # * MTX_SEGMENT_PATH: segment file path + runOnRecordSegmentCreate: + + # Command to run when a recording segment is complete. + # The following environment variables are available: + # * MTX_PATH: path name + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + # * MTX_SEGMENT_PATH: segment file path + # * MTX_SEGMENT_DURATION: segment duration + runOnRecordSegmentComplete: + +############################################### +# Path settings + +# Settings in "paths" are applied to specific paths, and the map key +# is the name of the path. +# Any setting in "pathDefaults" can be overridden here. +# It's possible to use regular expressions by using a tilde as prefix, +# for example "~^(test1|test2)$" will match both "test1" and "test2", +# for example "~^prefix" will match all paths that start with "prefix". +paths: + # example: + # my_camera: + # source: rtsp://my_camera + dwarf_wide: + source: rtsp://192.168.0.252:554/ch1/stream0 + sourceOnDemand: yes + sourceOnDemandCloseAfter: 10s + record: no + dwarf_tele: + source: rtsp://192.168.0.252:554/ch0/stream0 + sourceOnDemand: yes + sourceOnDemandCloseAfter: 10s + record: no + + # Settings under path "all_others" are applied to all paths that + # do not match another entry. + all_others: diff --git a/package.json b/package.json index 2916442..f591e0e 100644 --- a/package.json +++ b/package.json @@ -91,5 +91,10 @@ "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "typescript": "^5.0.4" - } + }, + "description": "![GitHub issues](https://img.shields.io/github/issues/stevejcl/dwarfii-stellarium-goto)\r ![GitHub last commit](https://img.shields.io/github/last-commit/stevejcl/dwarfii-stellarium-goto)\r ![GitHub](https://img.shields.io/github/license/stevejcl/dwarfii-stellarium-goto)\r ![downloads](https://img.shields.io/github/downloads/stevejcl/dwarfii-stellarium-goto/total.svg)\r [![Discord](https://dcbadge.vercel.app/api/server/5vFWbsXDfv)](https://discord.gg/5vFWbsXDfv)\r ![Repobeats](https://repobeats.axiom.co/api/embed/14963aa4fc5307591a6e387817c1dedf75d7e8f9.svg \"Repobeats analytics image\")", + "main": "next.config.js", + "keywords": [], + "author": "", + "license": "ISC" } diff --git a/public/DWARFIUM.ico b/public/DWARFIUM.ico new file mode 100644 index 0000000..a8d7062 Binary files /dev/null and b/public/DWARFIUM.ico differ diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index f5d1d09..3aff0af 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -69,4 +69,4 @@ } ] } -} +} \ No newline at end of file diff --git a/src/components/DwarfCameras.module.css b/src/components/DwarfCameras.module.css index 279d6a6..687c75c 100644 --- a/src/components/DwarfCameras.module.css +++ b/src/components/DwarfCameras.module.css @@ -1,12 +1,28 @@ .telephoto { width: 100%; + z-index : 50; +} + +.telephoto iframe { + width: 100%; + height: auto; /* Ensures proportional resizing */ + display: block; /* Removes unwanted spaces between elements */ } .wideangle { - width: 30%; + width: 35%; + height: 35%; position: absolute; top: 0; border: 1px solid white; + z-index : 60; +} + +.wideangle iframe { + width: 100%; + height: 100%; + border: none; + display: block; /* Removes unwanted spaces between elements */ } .section { diff --git a/src/components/DwarfCameras.tsx b/src/components/DwarfCameras.tsx index 1b4dd6b..4f5c68b 100644 --- a/src/components/DwarfCameras.tsx +++ b/src/components/DwarfCameras.tsx @@ -1,6 +1,7 @@ /* eslint-disable @next/next/no-img-element */ import { useState, useContext, useEffect, useRef } from "react"; +import type { Dispatch, SetStateAction } from "react"; import { TransformWrapper, TransformComponent, @@ -13,13 +14,17 @@ import { DwarfIP, wideangleURL, telephotoURL, - rawPreviewURL, messageCameraTeleGetSystemWorkingState, messageCameraTeleOpenCamera, messageCameraWideOpenCamera, WebSocketHandler, } from "dwarfii_api"; +import Image from "next/image"; +import { StaticImageData } from "next/image"; +import imgTeleCameraSrc from "/public/images/dwarflab_camera.png"; +import imgWideCameraSrc from "/public/images/dwarfII.png"; + import styles from "@/components/DwarfCameras.module.css"; import { ConnectionContextType } from "@/types"; import { logger } from "@/lib/logger"; @@ -31,15 +36,26 @@ import { } from "@/lib/dwarf_utils"; type PropType = { + setExchangeCamerasStatus: Dispatch>; showWideangle: boolean; useRawPreviewURL: boolean; showControls: boolean; }; export default function DwarfCameras(props: PropType) { - const { showWideangle, useRawPreviewURL, showControls } = props; + const { + setExchangeCamerasStatus, + showWideangle, + useRawPreviewURL, + showControls, + } = props; let connectionCtx = useContext(ConnectionContext); + // const wideangleURL_D3 = "http://localhost:8083/static/wide_angle_stream.html"; + // const telePhotoURL_D3 = "http://localhost:8083/static/tele_stream.html"; + const wideangleURL_D3 = "http://127.0.0.1:8888/dwarf_wide"; + const telePhotoURL_D3 = "http://127.0.0.1:8888/dwarf_tele/"; + const [errorTxt, setErrorTxt] = useState(""); const [telephotoCameraStatus, setTelephotoCameraStatus] = useState< string | undefined @@ -47,29 +63,78 @@ export default function DwarfCameras(props: PropType) { const [wideangleCameraStatus, setWideangleCameraStatus] = useState< string | undefined >("off"); - const [wideCameraSrc, setWideCameraSrc] = useState(""); - const [teleCameraSrc, setTeleCameraSrc] = useState(""); + + const defaultTeleCameraSrc: StaticImageData = imgTeleCameraSrc; + const defaultWideCameraSrc: StaticImageData = imgWideCameraSrc; + console.info("Render Wide SRC : ", defaultWideCameraSrc.src); + console.info("Render Tele SRC : ", defaultTeleCameraSrc.src); + + const [wideCameraSrc, setWideCameraSrc] = useState( + defaultWideCameraSrc.src + ); + const [teleCameraSrc, setTeleCameraSrc] = useState( + defaultTeleCameraSrc.src + ); let lastRenderTime = useRef(Date.now()); let IPDwarf = connectionCtx.IPDwarf || DwarfIP; - const defaultTeleCameraSrc = "/images/dwarflab_camera.png"; - const defaultWideCameraSrc = "/images/dwarfII.png"; const [teleCameraClass, setTeleCameraClass] = useState(styles.telephoto); const [wideCameraClass, setWideCameraClass] = useState(styles.wideangle); + const iframeRefTele = useRef(null); + const iframeRefWide = useRef(null); + const iImgRefWide = useRef(null); + const imgTeleRef = useRef(null); + const imgWideRef = useRef(null); + + // Function to adjust iframe size to match the image + const adjustIframeSize = () => { + if ( + imgTeleRef.current && + imgTeleRef.current.clientWidth != 0 && + iframeRefTele.current + ) { + const imgWidth = imgTeleRef.current.clientWidth; + const imgHeight = imgTeleRef.current.clientHeight; + console.error(`imgWidth ${imgWidth}px`); + iframeRefTele.current.style.width = `${imgWidth}px`; + iframeRefTele.current.style.height = `${imgHeight}px`; + console.error(` set imgWidth ${imgWidth}px set imgHeight ${imgHeight}px`); + } + console.error("End Of adjustIframeSize"); + }; + useEffect(() => { console.debug("Start Of Effect DwarfCameras"); checkCameraStatus(connectionCtx); return () => { + setExchangeCamerasStatus(false); setWideangleCameraStatus("off"); setTelephotoCameraStatus("off"); - setWideCameraSrc(defaultWideCameraSrc); - setTeleCameraSrc(defaultTeleCameraSrc); + setWideCameraSrc(defaultWideCameraSrc.src); + setTeleCameraSrc(defaultTeleCameraSrc.src); + console.log( + `Device type read: ${connectionCtx.typeIdDwarf} - ${ + connectionCtx.typeIdDwarf === 1 ? "Dwarf II" : "Dwarf 3" + }` + ); console.debug("End Of Effect DwarfCameras"); }; }, []); // eslint-disable-line react-hooks/exhaustive-deps + function getWideAngleURL() { + if (!connectionCtx.typeIdDwarf || connectionCtx.typeIdDwarf == 1) + return wideangleURL(IPDwarf); + else return wideangleURL_D3; + } + + function getTelePhotoURL() { + if (!connectionCtx.typeIdDwarf || connectionCtx.typeIdDwarf == 1) + return telephotoURL(IPDwarf); + else return telePhotoURL_D3; + } + function turnOnCameraHandler(cameraId: number, connectionCtx) { if (cameraId === telephotoCamera) { turnOnTeleCameraFn( @@ -87,9 +152,11 @@ export default function DwarfCameras(props: PropType) { } function checkCameraStatus(connectionCtx: ConnectionContextType) { - if (wideCameraSrc !== defaultWideCameraSrc) setWideangleCameraStatus("on"); + if (wideCameraSrc && wideCameraSrc !== defaultWideCameraSrc.src) + setWideangleCameraStatus("on"); else setWideangleCameraStatus("off"); - if (teleCameraSrc !== defaultTeleCameraSrc) setTelephotoCameraStatus("on"); + if (teleCameraSrc && teleCameraSrc !== defaultTeleCameraSrc.src) + setTelephotoCameraStatus("on"); else setTelephotoCameraStatus("off"); setTimeout(() => { checkCameraStatusLater(connectionCtx); @@ -188,6 +255,16 @@ export default function DwarfCameras(props: PropType) { if (connectionCtx.IPDwarf === undefined) { return; } + adjustIframeSize(); + // Slave Mode turn on Camera + if (connectionCtx.connectionStatusSlave) { + setWideangleCameraStatus("on"); + setSrcWideCamera(true); + setTelephotoCameraStatus("on"); + setSrcTeleCamera(true); + return; + } + console.log("socketIPDwarf: ", connectionCtx.socketIPDwarf); // Create WebSocketHandler if need const webSocketHandler = connectionCtx.socketIPDwarf ? connectionCtx.socketIPDwarf @@ -222,10 +299,10 @@ export default function DwarfCameras(props: PropType) { function setSrcWideCamera(status: boolean) { console.info("Render setSrcWideCamera : ", status); if (status) { - const url = wideangleURL(IPDwarf); + const url: string = getWideAngleURL(); setWideCameraSrc(url); } else { - const url = ""; + const url: string = defaultWideCameraSrc.src; setWideCameraSrc(url); } } @@ -234,10 +311,10 @@ export default function DwarfCameras(props: PropType) { function setSrcTeleCamera(status: boolean) { console.info("Render setSrcTeleCamera : ", status); if (status) { - const url = rawPreviewURL(IPDwarf); + const url: string = getTelePhotoURL(); setTeleCameraSrc(url); } else { - const url = "/images/dwarlab_camera.png"; + const url: string = defaultTeleCameraSrc.src; setTeleCameraSrc(url); } } @@ -247,21 +324,70 @@ export default function DwarfCameras(props: PropType) { console.info("Render SRC : ", wideCameraSrc); return (
- - wideCameraSrc !== defaultWideCameraSrc - ? setWideangleCameraStatus("on") - : setWideangleCameraStatus("off") - } - src={wideCameraSrc} - alt={wideCameraSrc ? "livestream for wideangle camera" : ""} - className={wideCameraClass} - > + {wideCameraSrc +
+ {!connectionCtx.typeIdDwarf || connectionCtx.typeIdDwarf == 1 ? ( + { + wideCameraSrc !== defaultWideCameraSrc.src + ? setWideangleCameraStatus("on") + : setWideangleCameraStatus("off"); + }} + src={wideCameraSrc} + alt={wideCameraSrc ? "" : ""} + ref={iImgRefWide} // Reference to the image element + /> + ) : ( + // Render + )} +
); } + const handleImageLoad = () => { + // Only hide the image and show the iframe after adjusting the size + if (teleCameraSrc !== defaultTeleCameraSrc.src) { + adjustIframeSize(); + setTelephotoCameraStatus("on"); + } else { + setTelephotoCameraStatus("off"); + } + }; + function renderMainCamera() { let newRenderTime = Date.now(); @@ -277,26 +403,89 @@ export default function DwarfCameras(props: PropType) { // TODO: use rawPreviewURL vs telephotoURL, return (
- - teleCameraSrc !== defaultTeleCameraSrc - ? setTelephotoCameraStatus("on") - : setTelephotoCameraStatus("off") - } src={teleCameraSrc} + width="1280" + height="720" alt={teleCameraSrc ? "livestream for telephoto camera" : ""} - className={teleCameraClass} - > + ref={imgTeleRef} // Reference to the image element + onLoad={() => handleImageLoad()} + /> +
+ +
); } function exchangeCameras() { + if (!showWideangle) return; + // Swap the classes const tempClass = teleCameraClass; setTeleCameraClass(wideCameraClass); setWideCameraClass(tempClass); + + if (!connectionCtx.typeIdDwarf || connectionCtx.typeIdDwarf == 1) { + if (iImgRefWide.current && iframeRefTele.current) { + const tempStyleWidth = iImgRefWide.current.clientWidth; + const tempStyleHeight = iImgRefWide.current.clientHeight; + const iframeWidth = parseFloat(iframeRefTele.current.style.width) || 0; // Default to 0 if NaN + iImgRefWide.current.width = iframeWidth; + const iframeHeight = + parseFloat(iframeRefTele.current.style.height) || 0; // Default to 0 if NaN + iImgRefWide.current.height = iframeHeight; + iframeRefTele.current.style.width = `${tempStyleWidth}px`; + iframeRefTele.current.style.height = `${tempStyleHeight}px`; + } + } else { + if (iframeRefWide.current && iframeRefTele.current) { + const tempStyleWidth = iframeRefWide.current.clientWidth; + const tempStyleHeight = iframeRefWide.current.clientHeight; + console.error(iframeRefWide.current.clientWidth); + console.error(iframeRefWide.current.clientHeight); + iframeRefWide.current.style.width = iframeRefTele.current.style.width; + iframeRefWide.current.style.height = iframeRefTele.current.style.height; + iframeRefTele.current.style.width = `${tempStyleWidth}px`; + iframeRefTele.current.style.height = `${tempStyleHeight}px`; + } + } + setExchangeCamerasStatus((prev) => !prev); + + // Not Recording + if ( + !connectionCtx.imagingSession.isRecording && + !connectionCtx.imagingSession.endRecording && + !connectionCtx.imagingSession.isGoLive && + connectionCtx.typeIdDwarf != 1 + ) { + connectionCtx.setCurrentAstroCamera( + connectionCtx.currentAstroCamera == telephotoCamera + ? wideangleCamera + : telephotoCamera + ); + } } const Controls = () => { @@ -346,16 +535,16 @@ export default function DwarfCameras(props: PropType) { - {wideangleURL(IPDwarf)} + {getWideAngleURL()} - {telephotoURL(IPDwarf)} + {getTelePhotoURL()} @@ -366,9 +555,9 @@ export default function DwarfCameras(props: PropType) { - {wideangleURL(IPDwarf)} + {getWideAngleURL()} @@ -379,9 +568,9 @@ export default function DwarfCameras(props: PropType) { - {telephotoURL(IPDwarf)} + {getTelePhotoURL()} diff --git a/src/components/imaging/CameraAddOn.tsx b/src/components/imaging/CameraAddOn.tsx index 37bdbf3..5ae542d 100644 --- a/src/components/imaging/CameraAddOn.tsx +++ b/src/components/imaging/CameraAddOn.tsx @@ -47,7 +47,7 @@ export default function CameraAddOn(props: PropTypes) { let connectionCtx = useContext(ConnectionContext); const { showModal, setShowModal } = props; - const [imgSrc] = useState("/images/photo-camera-white.png"); + const [imgSrc] = useState("/public/images/photo-camera-white.png"); const [errorTxt, setErrorTxt] = useState(""); const [oldErrorTxt, setOldErrorTxt] = useState(""); const [isVisible, setIsVisible] = useState(true); @@ -312,10 +312,10 @@ export default function CameraAddOn(props: PropTypes) { function changeColorButton(ImgID, Force = false) { const imgElementButton = document.getElementById(ImgID) as HTMLImageElement; if (imgElementButton) { - if (Force) imgElementButton.src = "/images/photo-camera-red.png"; + if (Force) imgElementButton.src = "/public/images/photo-camera-red.png"; else if (imgElementButton.src.includes("photo-camera-white")) - imgElementButton.src = "/images/photo-camera-red.png"; - else imgElementButton.src = "/images/photo-camera-white.png"; + imgElementButton.src = "/public/images/photo-camera-red.png"; + else imgElementButton.src = "/public/images/photo-camera-white.png"; } } @@ -330,12 +330,12 @@ export default function CameraAddOn(props: PropTypes) { // Change the image source using the ID const imgElement = document.getElementById("TakePhoto") as HTMLImageElement; if (imgElement) { - imgElement.src = "/images/photo-camera-red.png"; + imgElement.src = "/public/images/photo-camera-red.png"; } // Reset the image source back to its original source after a delay setTimeout(() => { if (imgElement) { - imgElement.src = "/images/photo-camera-white.png"; + imgElement.src = "/public/images/photo-camera-white.png"; } }, 2000); // Reset the active action after the photo is taken @@ -1321,7 +1321,7 @@ export default function CameraAddOn(props: PropTypes) { )}
Settings) { @@ -272,6 +282,65 @@ export default function TakeAstroPhoto(props: PropTypes) { } } + function changeWideGainHandler(e: ChangeEvent) { + let targetValue = e.target.value; + if (targetValue === "default") { + defaultValueHandler("wideGain"); + return; + } + + let value = Number(targetValue); + + setTimeout(() => { + connectionCtx.setAstroSettings((prev) => { + prev["wideGain"] = value; + return { ...prev }; + }); + saveAstroSettingsDb("wideGain", targetValue); + updateTelescopeISPSetting("wideGain", value, connectionCtx); + }, 1000); + } + + function changeWideExposureHandler(e: ChangeEvent) { + let targetValue = e.target.value; + if (targetValue === "default") { + defaultValueHandler("wideExposure"); + return; + } + + let value: number; + let modeValue: number; + + if (targetValue === "auto") { + modeValue = modeAuto; + value = Number(getWideExposureDefault(connectionCtx.typeIdDwarf)); + } else { + modeValue = modeManual; + value = Number(targetValue); + } + + connectionCtx.setAstroSettings((prev) => { + prev["wideExposureMode"] = modeValue; + return { ...prev }; + }); + saveAstroSettingsDb("wideExposureMode", modeValue.toString()); + updateTelescopeISPSetting("wideExposureMode", modeValue, connectionCtx); + + setTimeout(() => { + connectionCtx.setAstroSettings((prev) => { + if (targetValue === "auto") { + prev["wideExposure"] = targetValue; + } else { + prev["wideExposure"] = value; + } + return { ...prev }; + }); + saveAstroSettingsDb("wideExposure", targetValue); + if (targetValue != "auto") + updateTelescopeISPSetting("wideExposure", value, connectionCtx); + }, 500); + } + function toggleShowSettingsInfo() { setShowSettingsInfo(!showSettingsInfo); } @@ -309,6 +378,31 @@ export default function TakeAstroPhoto(props: PropTypes) { }; const allowedIROptions = generateIROptions(connectionCtx.typeIdDwarf); //DwarfModelId + // Function to generate options for a specific Dwarf model + const generateWideExposureOptions = (DwarfModelId = 1) => { + const exposures = allowedWideExposures[DwarfModelId]; + return exposures.values.map(({ index, name }) => ( + + )); + }; + const allowedWideExposuresOptions = generateWideExposureOptions( + connectionCtx.typeIdDwarf + ); //DwarfModelId + + const generateWideGainOptions = (DwarfModelId = 1) => { + const gains = allowedWideGains[DwarfModelId]; + return gains.values.map(({ index, name }) => ( + + )); + }; + const allowedWideGainsOptions = generateWideGainOptions( + connectionCtx.typeIdDwarf + ); //DwarfModelId + if (showSettingsInfo) { return ; } @@ -330,6 +424,9 @@ export default function TakeAstroPhoto(props: PropTypes) { AiEnhance: connectionCtx.astroSettings.AiEnhance, target: connectionCtx.astroSettings.target, status: connectionCtx.astroSettings.status, + wideGain: connectionCtx.astroSettings.wideGain, + wideExposureMode: connectionCtx.astroSettings.wideExposureMode, + wideExposure: connectionCtx.astroSettings.wideExposure, }} validate={(values) => { let errors = validateAstroSettings(values); @@ -344,102 +441,184 @@ export default function TakeAstroPhoto(props: PropTypes) { > {({ values, errors, handleChange, handleBlur, handleSubmit }) => (
-
-
- {t("cAstroSettings")}{" "} - -
- -
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- + {connectionCtx.currentAstroCamera == telephotoCamera && ( +
+
+ {t("cAstroSettings")}{" "} + +
-
- + )} + {connectionCtx.currentAstroCamera == wideangleCamera && ( +
+
+ {t("cAstroSettingsWide")}{" "} + +
-
-
-
- + )} + {connectionCtx.currentAstroCamera == telephotoCamera && ( +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
-
- + )} + {connectionCtx.currentAstroCamera == wideangleCamera && ( +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
-
+ )}