Compare commits

...

565 Commits

Author SHA1 Message Date
Sergey Krashevich 812fa91d83 fix(api): improve CORS and preflight request handling
- add support for handling OPTIONS method in middleware functions
- ensure proper CORS headers are set for preflight requests
2026-04-22 04:01:38 +03:00
Sergey Krashevich ad2c09abf4 Fix link reference for pull request #2168 2026-03-23 16:17:16 +03:00
Sergey Krashevich 5907eadacf Remove socket leaks pull request reference
Removed a pull request reference related to socket leaks in go2rtc.
2026-03-23 16:08:18 +03:00
Sergey Krashevich 046096826b fix(webrtc): handle client connection errors gracefully (#2173)
- add deferred function to close the client connection on error
- ensure proper cleanup for all client functions
2026-03-23 16:05:58 +03:00
Sergey Krashevich 4a65f06864 feat(docs): add WebCodecs streaming feature to README
- include WebCodecs API support for browser-native video/audio decoding
- update feature list and examples table for clarity
2026-03-21 15:53:42 +03:00
Sergey Krashevich da908ca6c1 refactor after #2160 2026-03-21 13:48:26 +03:00
seydx 0b6ac18043 Implement WebCodecsPlayer for video/audio decoding and rendering 2026-03-21 13:48:25 +03:00
seydx 5efcfb741b Update timestamp handling 2026-03-21 13:48:25 +03:00
seydx 30c72ff3f5 Add VideoRenderer class for cascading video rendering with WebGPU, WebGL2, and Canvas 2D support 2026-03-21 13:48:25 +03:00
seydx efc5eff40f Refactor timestamp handling in buildFrame and AddTrack functions to use microseconds 2026-03-21 13:48:25 +03:00
seydx 262a571a84 add webcodecs support 2026-03-21 13:48:25 +03:00
Sergey Krashevich c90480a40c Merge branch 'master' into dev 2026-03-21 11:02:11 +03:00
Alex X dc1685e9cf Merge pull request #2159 from ludufre/fix/mpegts-pcma-pcmu-probe
Add support PCMATapo for MPEG-TS source
2026-03-17 17:29:03 +03:00
Luan Freitas 65af579770 fix: remove PCMU, keep only PCMA (always 8000 Hz per G.711 spec) 2026-03-17 10:27:23 -03:00
Sergey Krashevich 6482373aa3 chore(build): add dev branch to build workflow - include 'dev' branch to trigger builds alongside 'beta' docs(README): update binary download links to dev builds - change links to point to nightly builds for development binaries 2026-03-17 04:36:04 +03:00
Sergey Krashevich d6a4d1e8e0 docs(README): update change log to include latest PR 2159 - add entry for PR 2159 regarding PCMA and PCMU handling by @ludufre 2026-03-17 04:27:13 +03:00
Sergey Krashevich 107924d2f7 Merge pull request #2159 from ludufre/fix/mpegts-pcma-pcmu-probe 2026-03-17 04:13:14 +03:00
Luan Freitas d9af354de8 fix: add PCMU to StreamType() mapping 2026-03-16 20:23:01 -03:00
Luan Freitas 31c19a0e7d fix: incoming pcma and pcmu 2026-03-16 18:33:01 -03:00
Sergey Krashevich 8f3be3050b docs(README): add section for merged PRs not yet in master 2026-03-11 13:38:31 +03:00
Sergey Krashevich 6a968c8595 docs(README): update Docker image references from skrashevich to AlexxIT for consistency 2026-03-11 13:30:34 +03:00
Sergey Krashevich cc5f177314 Merge pull request #2147 from skrashevich/beta
dev branch: merge from skrashevich/go2rtc:beta
2026-03-11 12:02:11 +03:00
Sergey Krashevich f1d4baa5d0 docs(README): update features and improvements sections - add WebUI enhancements and clarify system resource monitoring details 2026-03-11 02:17:42 +03:00
Sergey Krashevich 5d6b4911ff docs(README): add screenshots and update image references - include dark and light theme screenshots for various app components - update image links in the documentation for consistency feat(package): add playwright as a dependency - include playwright for end-to-end testing capabilities 2026-03-11 02:13:15 +03:00
Sergey Krashevich 27b04ed9d8 style(links, add, index, info, probe, log, main, styles): update styles and improve layout - refactor button and input styles for consistency across pages - enhance responsive design for better usability on mobile devices - adjust spacing and margins for a cleaner look - unify color scheme and hover effects for links and buttons - remove unused font imports to optimize loading performance 2026-03-11 02:12:52 +03:00
Sergey Krashevich 5fced9a580 Update README files to reflect new Docker image naming and features in the fork 2026-03-11 00:24:33 +03:00
Sergey Krashevich d4c2721d1c Update Go version to 1.26 in workflows and Dockerfiles 2026-03-11 00:17:31 +03:00
Sergey Krashevich 9f5a4b2d96 Publish to GHCR only; remove DockerHub logins
Simplify CI container publishing by targeting only GitHub Container Registry. The docker/metadata-action images inputs for meta, meta-hw and meta-rk jobs were changed to ghcr.io/${{ github.repository }} (removing DockerHub image entries). DockerHub login steps were removed, and the GHCR login now runs for all non-pull_request events (if: github.event_name != 'pull_request'). Changes are in .github/workflows/build.yml to avoid pushing to DockerHub and ensure GHCR auth runs on appropriate events.
2026-03-11 00:15:37 +03:00
Sergey Krashevich 002070991c Merge remote-tracking branch 'origin/251221-webui-redisign' into beta
# Conflicts:
#	www/index.html
#	www/links.html
#	www/main.js
2026-03-11 00:02:12 +03:00
Sergey Krashevich 072cdd7c44 Merge remote-tracking branch 'origin/260201-readonly' into beta
# Conflicts:
#	README.md
#	www/index.html
2026-03-10 23:58:57 +03:00
Sergey Krashevich e59b17d77d Merge remote-tracking branch 'origin/260216-include-js-to-docker' into beta 2026-03-10 23:56:49 +03:00
Sergey Krashevich 7208e33aeb Merge remote-tracking branch 'origin/260213-add-pid-to-api' into beta 2026-03-10 23:56:45 +03:00
Sergey Krashevich 2141cce5e0 Merge remote-tracking branch 'origin/260213-fix-schemas-race-condition' into beta 2026-03-10 23:56:37 +03:00
Sergey Krashevich 21b9b49c06 Merge remote-tracking branch 'origin/260210-fix-yaml-patch-merge' into beta 2026-03-10 23:56:30 +03:00
Sergey Krashevich 256befb592 Merge branch 'go-webp' into beta
# Conflicts:
#	.gitignore
#	go.sum
2026-03-10 23:54:17 +03:00
Sergey Krashevich 1f2396e278 Merge branch 'hksv' into beta
# Conflicts:
#	pkg/hap/hds/hds_test.go
2026-03-10 23:53:40 +03:00
Sergey Krashevich 2b7682cdb3 refactor(dvrip): simplify broadcast loop structure
- replace traditional for loop with range-based for loop for clarity

refactor(ffmpeg): simplify cut function loop
- utilize range-based for loop instead of traditional for loop

refactor(ring): update API response mapping type
- change map type from `interface{}` to `any` for better type safety

refactor(stream): handle nil source in NewStream
- add nil check for source elements before processing

refactor(webrtc): unify payload handling in GetToken
- change map type from `interface{}` to `any` for consistency

refactor(ascii): optimize nested loops in Write function
- replace traditional for loops with range-based for loops for readability

refactor(bits): enhance readability in Reader methods
- replace traditional for loops with range-based for loops in Read functions

refactor(h264): modernize loop structures in DecodeConfig
- switch to range-based for loops for cleaner code

refactor(h265): streamline profile_tier_level loops
- utilize range-based for loops instead of traditional for loops

chore(core): remove commented-out test function for clarity

refactor(core): simplify RandString function loop
- change traditional for loop to range-based for loop

refactor(flvt): optimize timestamp handling in TestTimeToRTP
- switch to range-based for loop for iterating frames

refactor(nest): improve error handling in ExchangeSDP
- format error message with printf-style formatting for clarity

refactor(tapo): enhance securityEncode function
- change traditional for loop to range-based for loop for readability

fix(tcp): correct masking in websocket Write method
- replace traditional for loop with range-based for loop

refactor(tutk): modernize encoding loops in crypto functions
- utilize range-based for loops for better readability

refactor(tuya): unify data types in MQTT message struct
- change map type from `interface{}` to `any` for consistency

refactor(webrtc): standardize codec registration
- change map type from `interface{}` to `any` for type safety

refactor(yaml): simplify Unmarshal function signature
- update parameter type from `interface{}` to `any` for better clarity
2026-03-10 23:26:45 +03:00
Sergey Krashevich 319fc3a154 Update Go version to 1.26.1 and remove unused dependencies 2026-03-10 23:03:22 +03:00
Sergey Krashevich f435b49652 chore(go.mod): remove unused dependencies - delete github.com/asticode/go-astits and github.com/asticode/go-astikit from go.mod and go.sum to clean up unused imports - update the project dependencies for better maintainability 2026-03-10 22:56:42 +03:00
Sergey Krashevich 0d8d4c204d fix(go.mod, go.sum): update go-webp dependency to v0.1.0 2026-03-10 22:54:14 +03:00
Sergey Krashevich ea03aa832d feat(webp): add WebP streaming and snapshot APIs
- implement WebP streaming with multipart support
- add snapshot endpoint for WebP format with quality options
- introduce WebP encoding using pure Go library without FFmpeg
- update documentation and links for new WebP features
2026-03-10 22:43:29 +03:00
Sergey Krashevich 506cfa7df5 fix(.gitignore): add .omc to ignore list and ensure .ruff* is properly included 2026-03-10 22:29:03 +03:00
Sergey Krashevich e2e593ea3a feat(schema): add new motion detection options and properties - extend motion detection modes to include 'onvif' for event subscription - introduce 'motion_hold_time' to manage active motion duration - add 'onvif_url' for specifying ONVIF device URL for detection 2026-03-10 22:28:29 +03:00
Sergey Krashevich 8a21809f18 feat(homekit): add ONVIF motion detection support
- implement ONVIF motion watcher to handle motion events
- add configuration options for motion hold time and ONVIF URL
- remap motion mode from "onvif" to "api" for compatibility
- log ONVIF motion watcher activity for better debugging

feat(onvif): implement event subscription for motion detection
- create PullPoint subscription to receive motion events
- implement methods for pulling messages and renewing subscriptions
- handle event requests and responses specific to motion detection

test(onvif): add unit tests for motion event parsing and subscription
- create tests for parsing various motion event XML responses
- verify correct handling of multiple notifications and edge cases
- test resolving event addresses for ONVIF clients

fix(hksv): improve motion detection logging
- log warnings when accessory or character not found during motion detection
- log number of listeners notified during motion state changes

feat(hap): add listener count method
- introduce method to retrieve the number of listeners for a character

feat(onvif): enhance ONVIF client with event URL handling
- extract event URL from ONVIF device response for subscription management
2026-03-09 13:06:57 +03:00
Sergey Krashevich e3d1085a6d feat(hksv): add motion detection API and enhance server handling for consumers 2026-03-07 18:54:50 +03:00
Sergey Krashevich 9c901dc995 feat(hksv): add example CLI application for exporting RTSP cameras as HKSV 2026-03-06 20:54:53 +03:00
Sergey Krashevich c1ff7464d0 Add unit tests for HKSV 2026-03-06 20:48:32 +03:00
Sergey Krashevich c5b7ba7162 feat(hksv): add example CLI application for RTSP to HomeKit Secure Video streaming 2026-03-06 20:39:47 +03:00
Sergey Krashevich c567831c91 feat: add motion detection feature with EMA-based P-frame size analysis
- Implemented MotionDetector for detecting motion based on H.264 P-frame sizes.
- Introduced adjustable sensitivity threshold for motion detection.
- Added tests for various scenarios including motion detection, hold time, cooldown, and baseline adaptation.
- Created hksvSession to manage HDS DataStream connections for HKSV recording.
- Updated schema.json to include a new speaker option for 2-way audio support.
2026-03-06 19:58:15 +03:00
Sergey Krashevich 593dce6eb9 feat(homekit): add Speaker service and enhance Consumer with backtrack audio handling 2026-03-06 09:07:26 +03:00
Sergey Krashevich 78ef8fc064 perf(homekit): optimize motion detector with frame-based timing
Replace time.Now() calls in hot path with frame-based timing:
- Pre-compute triggerLevel (integer comparison instead of float division)
- Calibrate hold/cooldown budgets from FPS (default 30fps)
- Periodic FPS recalibration every 150 frames for accuracy
- Active motion path: 47ns → 3.6ns (13x faster)

Update schema.json with detect mode and motion_threshold.
Add threshold tuning guide to README.
2026-03-05 08:50:27 +03:00
Sergey Krashevich a591186da6 test(homekit): add tests and benchmarks for HDS protocol and HKSV consumer
HDS protocol tests (15 tests, 4 benchmarks):
- Message structure for SendMediaInit and SendMediaFragment
- Multi-chunk splitting for fragments > 256KB
- Chunk boundary handling and sequence preservation
- WriteEvent/WriteResponse/WriteRequest round-trip
- opack helper functions

HKSV consumer tests (14 tests, 3 benchmarks):
- Consumer creation and field initialization
- GOP buffer flush with sequence numbering
- Activate with init segment and seqNum=2
- Activate timeout and error handling
- Stop safety (double-stop, deactivation)
- WriteTo blocking until Stop

Also fixes broken hds_test.go (undefined Client → NewConn).
2026-03-05 06:43:11 +03:00
Sergey Krashevich 1856b7ace4 fix(homekit): fix HKSV recording by correcting HDS protocol and adding GOP buffering
The HKSV recording was failing because:
1. The dataSend.data message structure was wrong - `packets` was a flat integer
   instead of an array of objects with `data` and `metadata` fields matching
   the HAP-NodeJS specification
2. Each video/audio frame was sent as a separate mediaFragment, but Home Hub
   expects GOP-based fragments (~2-4 seconds of accumulated data)
3. Large fragments were not chunked (max 256 KiB per chunk)

Changes:
- Fix HDS dataSend.data message structure to use proper packets array with
  nested data/metadata (dataType, dataSequenceNumber, dataChunkSequenceNumber,
  isLastDataChunk, dataTotalSize)
- Add 256 KiB chunking for large media fragments
- Buffer moof+mdat pairs in hksvConsumer and flush on keyframe boundaries
  (GOP-based fragmentation)
- Pre-start consumer at pair-verify for instant init segment delivery
- Add write-response support to HAP PUT handler for ch131 DataStream setup
- Fix HAP service linking to match HAP-NodeJS reference
- Add default SelectedCameraRecordingConfiguration (ch209) value
- Start continuous motion generator at pair-verify with dedup protection
2026-03-05 06:26:06 +03:00
Sergey Krashevich 35fd1383c8 fix(homekit): adjust motion detection threshold and improve hold time checks 2026-03-05 03:09:02 +03:00
Sergey Krashevich 15b0cc4c0c feat(homekit): implement motion detection with configurable threshold and add motion detector functionality 2026-03-04 15:08:06 +03:00
Sergey Krashevich 81dd9e37d8 chore: update .gitignore to include .claude* and .ruff* files 2026-03-04 14:20:33 +03:00
Sergey Krashevich ab27a042c1 feat(homekit): add HKSV support with motion detection and doorbell functionality
- Introduced HKSV configuration options in homekit.go, allowing for motion detection and doorbell features.
- Implemented API endpoints for triggering motion detection and doorbell events.
- Enhanced server.go to handle HKSV sessions and manage motion detection states.
- Created new accessory types for HKSV and doorbell in accessory.go.
- Added support for audio recording configurations in ch207.go.
- Defined new services for motion detection and doorbell in services_hksv.go.
- Implemented opack encoding/decoding for HDS protocol in opack.go and protocol.go.
- Updated OpenAPI documentation to reflect new endpoints and features.
- Extended schema.json to include HKSV configuration options.
2026-03-04 13:53:17 +03:00
Sergey Krashevich a0a36f87bd feat(tests): add CDN URL extraction and patching tests
- implement tests for extracting CDN URLs from HTML files
- add tests for parsing CDN URLs and patching HTML content
- ensure functionality works with real HTML files and CDN URLs
2026-02-16 05:20:25 +03:00
Sergey Krashevich 73c43dbf8b Add CDN dependency download script and update Dockerfiles for offline web UI 2026-02-16 04:50:24 +03:00
Sergey Krashevich 7748346b1f fix(index.html): respect 12-hour format 2026-02-14 07:26:53 +03:00
Sergey Krashevich 39febb67b6 feat(index.html): add time history for system info graph
- introduce timeHistory array to track timestamps
- implement formatClock function for time formatting
- create renderXAxisLabels function for x-axis labels
2026-02-13 15:47:08 +03:00
Sergey Krashevich 8d329fea2e feat(index.html): implement system info update handling
- add timer for periodic updates of system information
- enhance renderInfo function to handle unsupported usage
- improve updateInfo function to manage historical data updates
2026-02-13 15:34:40 +03:00
Sergey Krashevich cfc9c86a66 fix(index.html): replace hash symbol with block character in graph - change the character used for bars from '#' to '█' for better visual representation 2026-02-13 15:13:04 +03:00
Sergey Krashevich 8a083d6f53 feat(index.html): enhance system information display
- add ASCII graph rendering for CPU and memory usage
- implement history tracking for better visualization
- introduce error handling for data fetching

chore(main.js): define system info update interval
- set a common refresh interval for system info updates
2026-02-13 15:11:22 +03:00
Sergey Krashevich 0b80fa53cf feat(api): add system resource monitoring functionality
- implement getSystemInfo to gather CPU and memory usage
- add platform-specific implementations for memory and CPU usage
- enhance OpenAPI documentation to include system resource metrics
2026-02-13 14:49:20 +03:00
Sergey Krashevich beb96dd076 Merge remote-tracking branch 'upstream/master' 2026-02-13 14:07:55 +03:00
Sergey Krashevich ab80450b66 Expose process PID in API and UI
Include the process PID in the API info payload and surface it in the frontend. apiHandler now adds app.Info["pid"] = os.Getpid(); openapi.yaml documents the new pid property as an integer with an example; www/index.html is updated to show pid alongside version and config. This aids debugging and identifying the running process.
2026-02-13 11:12:00 +03:00
Alex X 01c7451556 Restore fix audio sample rate for some xiaomi cameras #2006 2026-02-13 10:59:38 +03:00
Sergey Krashevich c2d3963e93 Merge remote-tracking branch 'upstream/master' into 260213-fix-schemas-race-condition 2026-02-13 10:37:57 +03:00
Sergey Krashevich aa4e90d9d8 Ensure schemas wait for init 2026-02-13 10:37:20 +03:00
Sergey Krashevich 1f2c4b35ed fix(api): fix YAML merge functionality
- implement recursive merging of YAML with patch support
- preserve comments and formatting during merge operations
- add tests for various merge scenarios in config_test.go
2026-02-10 14:57:52 +03:00
Sergey Krashevich 74b92c2d78 chore(.gitignore): update ignored files list
- add CLAUDE.md to the ignored files for better cleanliness
2026-02-10 13:30:09 +03:00
Alex X f68c602a7d Add sitemap to website 2026-02-08 14:00:55 +03:00
Alex X 7f36033bff Merge pull request #2080 from skrashevich/260208-fix-grammar
Fix documentation grammar
2026-02-08 13:04:29 +03:00
Sergey Krashevich 6304987a26 Review uncommitted changes 2026-02-08 08:50:18 +03:00
Alex X 2735bafd86 Rename mpegts module to mpeg 2026-02-08 07:10:30 +03:00
Alex X 44f6f111c4 Move kasa source to separate module 2026-02-08 07:01:14 +03:00
Alex X e5bb03349b Add support pet feeder to xiaomi source #2078 2026-02-07 16:14:10 +03:00
Alex X e90f159c68 Add meta info to website 2026-02-07 13:19:11 +03:00
Alex X be4b6c3271 Update docs 2026-02-07 09:12:31 +03:00
Alex X f3ad4ad977 Fix links in docs 2026-02-07 00:08:47 +03:00
Alex X 7083afe9b2 Merge pull request #2044 from skrashevich/documentation-site
(website): new documentation site
2026-02-06 21:49:35 +03:00
Alex X d01b99d105 BIG docs refactoring 2026-02-06 21:45:55 +03:00
Sergey Krashevich 3984a074a1 webui: add back buttom to links.html 2026-02-04 06:23:39 +03:00
Sergey Krashevich 1b06558140 feat(styles): add external stylesheet for consistent theming and layout 2026-02-04 06:04:05 +03:00
Sergey Krashevich e37da9a056 style: update theme toggle styles to remove borders and animations 2026-02-04 05:28:12 +03:00
Sergey Krashevich c0a2e04ad0 feat(ui): add info and probe pages for detailed stream analysis 2026-02-04 05:15:33 +03:00
Sergey Krashevich 18cd71c602 fix(links): add word wrapping for mobile devices
Add word-break and overflow-wrap CSS properties to prevent long URLs
and stream names from breaking the layout on mobile devices. This fixes
horizontal overflow issues with long text in link items, code blocks,
and stream names.
2026-02-04 04:56:10 +03:00
Sergey Krashevich ab4955f8fc webui: fix header on mobiles 2026-02-03 13:53:49 +03:00
Alex X d1d7846aed Merge pull request #2064 from jmelancondev/webrtc/ice-server-urls-list
webrtc: Fix ice_servers parsing for a list of URLs
2026-02-03 13:42:13 +03:00
Sergey Krashevich 51a213c7da webui: links.html rewrite 2026-02-03 13:32:18 +03:00
Sergey Krashevich 0c91ca7113 webui: fix streams table 2026-02-03 13:25:49 +03:00
Sergey Krashevich c019924763 webui: navigation bar improve 2026-02-03 13:20:09 +03:00
Sergey Krashevich 3b33ffe2e2 webui: link to docs 2026-02-03 13:18:58 +03:00
Alex X 8d58cc7f97 Merge pull request #2065 from skrashevich/260201-small-fixes
Fix error handling in stream creation and patching tests
2026-02-03 13:15:16 +03:00
Sergey Krashevich 92eaaddcef style(config): update styles for main and config sections in HTML 2026-02-03 12:38:43 +03:00
Sergey Krashevich 3d439e030a fix(ui): toggle display of elements on click events in add.html 2026-02-03 12:22:19 +03:00
Sergey Krashevich 430dfc1c31 style: remove transition properties from index.html and main.js 2026-02-03 10:30:03 +03:00
Sergey Krashevich 85daab7ec6 style: update button colors to use CSS variables for improved theming 2026-02-03 10:20:56 +03:00
Sergey Krashevich fe5736905e feat: redesign web UI with theme toggle and improved styling 2026-02-03 09:51:09 +03:00
Sergey Krashevich bc7f9c0f79 feat: implement read-only mode enforcement in API handlers and add corresponding tests 2026-02-01 05:46:20 +03:00
Sergey Krashevich 51b79e614f feat: enhance read-only mode with confirmation and server polling on save 2026-02-01 04:38:48 +03:00
Sergey Krashevich f1d61bf15a fix: handle errors in stream creation and patching in tests 2026-02-01 04:18:04 +03:00
Sergey Krashevich cc453dd9ed feat: add read-only mode to API and UI, disable write actions 2026-02-01 04:13:43 +03:00
José Mélançon d2c4e44844 webrtc: Fix ice_servers parsing for a list of URLs 2026-01-31 14:25:13 -05:00
Sergey Krashevich 5bf5327a45 Merge branch 'AlexxIT:master' into documentation-site 2026-01-29 00:38:10 +03:00
Sergey Krashevich 4bf0aef124 Merge branch 'documentation-site' of https://github.com/skrashevich/go2rtc into documentation-site 2026-01-26 17:06:11 +03:00
Sergey Krashevich 8e01d5cfa9 🔧 chore(gh-pages): change default branch for deployment 2026-01-26 17:05:55 +03:00
Alex X 6085c8aabe Merge pull request #2058 from skrashevich/260126-fix-spelling
📝 docs(README): update documentation for clarity and accuracy
2026-01-26 11:23:35 +03:00
Sergey Krashevich 8d4869fd08 Merge branch 'AlexxIT:master' into documentation-site 2026-01-26 04:22:35 +03:00
Sergey Krashevich 5970dd2fe4 📝 docs(README): update documentation for clarity and accuracy 2026-01-26 00:40:48 +03:00
Alex X a41185c22a Merge pull request #2054 from jeis4wpi/master
Propose fix a couple of typos
2026-01-25 21:41:31 +03:00
Alex X c793855800 Merge pull request #2056 from martinbjeldbak/patch-1
docs: fix link to updated JSONSchema
2026-01-25 14:46:33 +03:00
Martin Bjeldbak Madsen a556bca7bd docs: fix link to updated JSONSchema 2026-01-25 19:57:58 +11:00
John E 051aa664cd Propose fix a couple of typos
Signed-off-by: John E <jeis4wpi@outlook.com>
2026-01-23 17:17:53 -05:00
Alex X 6374366da5 Add support xiaomi isa.camera.df3 on old firmwares 2026-01-21 22:26:16 +03:00
Alex X de157eb144 Strip Userinfo from log entries and errors #2051 2026-01-21 17:15:36 +03:00
Sergey Krashevich 72ec880300 Merge branch 'AlexxIT:master' into documentation-site 2026-01-21 15:56:17 +03:00
Alex X 6eeba1ae4c Merge pull request #1720 from felipecrs/update-readme
Update README
2026-01-21 07:23:37 +03:00
Alex X 8f68f80bff Add support xiaomi chuangmi.camera.ipc019e 2026-01-20 20:51:40 +03:00
Alex X 2f9d138692 Fix zero channel for xiaomi source #2047 2026-01-20 17:05:14 +03:00
Alex X 366cbd200d Fix unknown msg 0xF0 for cs2 proto 2026-01-20 14:24:27 +03:00
Alex X 6f75b2f75e Add trace log for xiaomi devices list 2026-01-20 14:19:51 +03:00
Alex X 75bd1f1b2b Improve packet loss on old TUTK proto 2026-01-20 14:19:18 +03:00
Alex X 7793a55853 Add support xiaomi lumi.camera.gwagl01 2026-01-20 14:18:03 +03:00
Alex X 06cbbe5543 Add support xiaomi isa.camera.isc5c1 2026-01-19 21:55:07 +03:00
Felipe Santos 861bc9728b Fix some other markdownlint violations 2026-01-19 14:24:59 -03:00
Felipe Santos ae0646ea9e Remove outdated section about Tuya cameras 2026-01-19 14:16:12 -03:00
Felipe Santos c8412eb067 Merge branch 'master' of https://github.com/AlexxIT/go2rtc into update-readme 2026-01-19 14:12:00 -03:00
Alex X a37fdf38d8 Add support xiaomi chuangmi.camera.v2 on old firmwares 2026-01-19 18:17:38 +03:00
Alex X b5948cfb25 Update version to 1.9.14 2026-01-19 11:59:09 +03:00
Alex X c64fcc55a5 Update dependencies 2026-01-19 11:28:09 +03:00
Sergey Krashevich ba9fb70929 feat(vitepress): enhance title extraction from files
📝 docs(ffmpeg): add title to ffmpeg README
2026-01-19 02:40:02 +03:00
Sergey Krashevich 1d9bb27f58 Update GitHub Pages workflow to use website directory
The workflow now copies built docs into the 'website' directory using rsync and uploads 'website' as the artifact instead of '.vitepress/dist'. This allows for additional processing or customization of the deployment directory.
2026-01-19 02:07:23 +03:00
Sergey Krashevich 55c1f5890b Refactor GitHub Pages workflow to separate build and deploy
Split the workflow into distinct 'build' and 'deploy' jobs. The build job handles checkout, install, and documentation build steps, while the deploy job depends on build and manages deployment to GitHub Pages. This improves workflow clarity and separation of concerns.
2026-01-19 01:53:55 +03:00
Sergey Krashevich d98059f069 Add VitePress documentation site setup
Introduces VitePress configuration and theme files for documentation, updates the GitHub Pages workflow to build and deploy the new docs, and updates .gitignore for VitePress and Node artifacts. Adds necessary dependencies and scripts to package.json, and updates the ONVIF client example README title.
2026-01-19 01:35:31 +03:00
Alex X fc22b20896 Fix ONVIF server support for Unifi Protect #1994 2026-01-18 22:47:04 +03:00
Alex X af819952e8 Improve ONVIF server support #1299 2026-01-18 21:51:05 +03:00
Alex X 159fb4675c Add starttimeout setting to exec source #1846 2026-01-18 16:07:46 +03:00
Alex X 54eafe9d0a Skip snapshot caching in case of error 2026-01-18 15:48:56 +03:00
Sergey Krashevich 38cc05c22d feat(jpeg): Add keyframe caching with expiration mechanism to JPEG http handler (#1155)
* feat(mjpeg): add keyframe caching with expiration and cleanup goroutine

* mjpeg: make keyframe cache duration and default usage configurable

* mjpeg: document and add config options for MJPEG snapshot caching

* mjpeg: fix errors after rebase

* Code refactoring for frame.jpeg cache #1155

---------

Co-authored-by: Alex X <alexey.khit@gmail.com>
2026-01-18 14:23:50 +03:00
Alex X 8c457710bd Merge pull request #2011 from seydx/wyze
Native Wyze camera support
2026-01-18 08:39:37 +03:00
Alex X 514188201a Move tutk dtls to separate package #2011 2026-01-18 08:38:38 +03:00
Alex X b65ee278cd Merge pull request #1991 from FIGIO55/onvif_info
Adding onvif name and hardware in info field of onvif discovery
2026-01-18 07:55:20 +03:00
Alex X 66225973ae Code refactoring for onvif device discovery #1991 2026-01-18 07:54:48 +03:00
seydx d40f6064d9 refactor dtls 2026-01-17 23:28:03 +01:00
seydx 9365fef7b3 move HL extraction to wyze client 2026-01-17 22:44:09 +01:00
seydx b220959e41 cleanup 2026-01-17 22:12:36 +01:00
seydx c493087876 fix intercom 2026-01-17 20:50:57 +01:00
seydx af90b4c12c update readme 2026-01-17 20:31:25 +01:00
seydx 679454aedb Merge branch 'master' into wyze 2026-01-17 20:31:05 +01:00
seydx 160695857e refactor 2026-01-17 20:22:08 +01:00
Alex X 6a5deecfcc Add multitrans source to readme 2026-01-17 21:53:37 +03:00
forrestsocool 572f07fcce Add two-way talk support for tp-link ipc camera (#1995)
* add tplink multitrans support

* 已经能成功播放音频了

* 初步能通讯了

* finish

* add tplink multitrans support

* 已经能成功播放音频了

* 初步能通讯了

* cleanup

* clean up

* Code refactoring for #1995

---------

Co-authored-by: Your Name <you@example.com>
Co-authored-by: Alex X <alexey.khit@gmail.com>
2026-01-17 21:39:49 +03:00
Aram Akhavan 9d1e4b11d7 Add EXPOSE to Dockerfile (#2010)
* Expose ports 1984 and 8554 in Dockerfile

* Update Dockerfile

* Update hardware.Dockerfile

* Expose additional ports in Rockchip Dockerfile

* Move docker expose ports to single line

---------

Co-authored-by: Alex X <alexey.khit@gmail.com>
2026-01-17 19:14:07 +03:00
Alex X f85cfdc214 Merge pull request #2019 from binary-person/patch-1
fix typo for inability to set channels for backchannel
2026-01-17 19:06:00 +03:00
Alex X 5780bf3720 Merge pull request #2023 from oeiber/patch-1
Update README with Doorbird support details
2026-01-17 18:41:28 +03:00
Alex X 338a3a6f03 Move doorbird docs to separate file 2026-01-17 18:40:18 +03:00
Alex X 6796bdabe2 Change header levels in readme 2026-01-17 18:30:06 +03:00
Alex X ebe454e3be Fix relative links in readme 2026-01-17 18:24:24 +03:00
seydx c03cd9f156 minor improvements 2026-01-17 12:09:38 +01:00
Alex X 1ec40f2fc3 Move schema.json to www/static 2026-01-17 13:57:05 +03:00
Alex X 790fdfbf7a Remove unnecessary codecs.html 2026-01-17 13:56:40 +03:00
Alex X 6d77b175d8 Move all JS libs to cdn.jsdelivr.net 2026-01-17 13:47:21 +03:00
seydx 8e6b8b32d6 Merge branch 'AlexxIT:master' into wyze 2026-01-17 09:40:49 +01:00
Alex X 425fcffbe1 Code refactoring for xiaomi source 2026-01-17 10:06:13 +03:00
Alex X 9a1eac8ef4 Add cache to xiaomi cloud logins 2026-01-17 09:41:41 +03:00
Alex X 4ffdeb06d0 Merge branch 'xiaomi'
# Conflicts:
#	pkg/xiaomi/miss/client.go
#	pkg/xiaomi/miss/cs2/conn.go
#	pkg/xiaomi/producer.go
2026-01-17 09:23:23 +03:00
Alex X 6b4eb8ffb6 Big rewrite tutk proto support 2026-01-17 09:05:54 +03:00
seydx cea524074a Merge branch 'wyze' of https://github.com/seydx/go2rtc into wyze 2026-01-15 22:52:04 +01:00
seydx 7498d0fba5 copy audio payload before processing in handleAudio function 2026-01-15 22:51:58 +01:00
seydx 50d9aab0d7 change pcm to pcml 2026-01-15 22:50:46 +01:00
seydx 3983ce3f4f Update Wyze Video Doorbell v2 details in README
Added model and version information for Wyze Video Doorbell v2.
2026-01-15 14:50:01 +01:00
seydx 0066da94f7 update wyze readme 2026-01-15 12:13:03 +01:00
seydx 4dbf53122e enable video and audio in buildK10002 again 2026-01-15 12:12:08 +01:00
seydx f96a074957 refactor timestamp handling 2026-01-15 01:25:35 +01:00
seydx dbd04cb972 refactor frame handling 2026-01-15 01:11:16 +01:00
seydx 0d035e5bce update ack hangling to improve streaming 2026-01-15 01:11:06 +01:00
seydx 7241759fea disable video and audio by default in buildK10002; start them later in probe 2026-01-15 01:10:41 +01:00
seydx b067c408c0 add missing codecs 2026-01-14 19:12:04 +01:00
seydx a99590823b allow to specify custom port 2026-01-14 19:11:53 +01:00
seydx 3bf2b316d7 Merge branch 'AlexxIT:master' into wyze 2026-01-13 21:50:10 +01:00
Alex X 2f43bfe5dc Fix two-way audio for cs2+tcp proto for xiaomi source 2026-01-13 21:26:01 +03:00
Alex X 59161fcef2 Improve cs2+udp proto for xiaomi source #2026 #2030 2026-01-13 21:25:30 +03:00
seydx 9ca9f96ea2 cleanup and update readme 2026-01-13 17:24:36 +01:00
seydx bc0c8d5577 use random session id in auth process 2026-01-13 16:48:41 +01:00
Alex X fd68107940 Add "bad conn" for debugging UDP 2026-01-13 12:15:48 +03:00
Alex X b19c081642 Improve cs2+udp proto for xiaomi source 2026-01-13 12:13:58 +03:00
seydx 039e916030 cleanup 2026-01-12 19:57:38 +01:00
seydx 3a587c9cee simplify SID generation 2026-01-12 13:56:52 +01:00
seydx 25e3125a89 Skip unsupported cameras (gwell based) 2026-01-12 11:22:25 +01:00
seydx 439dccf4bd cleanup 2026-01-12 11:03:53 +01:00
seydx 5fcb33c0cd Enhance video resolution handling by adding model-specific logic and updating subtype parsing 2026-01-12 11:00:28 +01:00
seydx c5311cdd94 Add keepalive command and sequence handling to new protocol 2026-01-12 10:34:05 +01:00
seydx 3dcb6dfc48 Merge branch 'AlexxIT:master' into wyze 2026-01-12 03:21:35 +01:00
seydx 406159cce5 refactor 2026-01-12 03:15:48 +01:00
seydx 659a042c42 Implement new authentication commands and improve PSK handling 2026-01-12 02:13:00 +01:00
Alex X e614513b97 Fix formats table in readme 2026-01-11 19:57:52 +03:00
Alex X f9f22cdd0b Add about OS versions to readme 2026-01-11 19:56:45 +03:00
Alex X dab9efb7d0 Merge pull request #2029 from Johnnybyzhang/master
xiaomi/cs2: fix TCP keepalive to match official Mi Home app
2026-01-11 19:55:23 +03:00
Alex X eb39b80883 Fix login to Xiaomi account with captcha #1985 2026-01-11 16:46:23 +03:00
Johnnybyzhang ff04a0d4b2 xiaomi/cs2: reduce TCP keepalive interval to 1 second
Based on PCAP analysis of official Mi Home app traffic:
- Official app sends PING every ~1 second
- Previous 5-second interval was too slow, causing connection resets

Simply reduce keepalive timeout from 5s to 1s.
2026-01-11 20:26:34 +08:00
Johnnybyzhang c4b32e3a0b xiaomi/cs2: fix TCP keepalive to match official Mi Home app
Based on PCAP analysis of official Mi Home app traffic, the keepalive
mechanism was incorrect:

Before (broken):
- Sent PING every 5s only when receiving data
- Responded to PING with PONG

After (fixed):
- Send PING every 1 second independently via dedicated goroutine
- Don't respond to PING with PONG (official app doesn't either)
- Both sides send PING bidirectionally as heartbeats

The official app sends 199 PING messages and 0 PONG messages in a
typical session. This fix matches that behavior.

Fixes connection resets after prolonged streaming sessions with
Xiaomi cameras using the CS2 P2P protocol.
2026-01-11 14:57:50 +08:00
seydx 58d8a86a92 cleanup 2026-01-10 10:46:26 +01:00
seydx 29f966f280 Fix intercom for new firmware 2026-01-10 03:13:41 +01:00
seydx cbaa147469 Update README.md for Wyze cameras 2026-01-10 02:44:27 +01:00
seydx c55fa87827 Update README.md to include details on NEW Protocol (0xCC51) for Wyze cameras 2026-01-10 02:44:07 +01:00
seydx 84e13d9d22 Add support for latest wyze firmware 2026-01-10 02:42:47 +01:00
oeiber c6733bf4f1 Update README with Doorbird support details
Added information about Doorbird device support and user permissions.
2026-01-07 22:20:40 +01:00
Alex X e960f90a97 Add formats and protocols to readme 2026-01-05 10:17:08 +03:00
Simon Cheng 3207f9e783 fix typo for inability to set channels for backchannel 2026-01-04 18:45:03 -05:00
seydx 263579fa01 cleanup 2026-01-03 16:12:37 +01:00
seydx 90c0b513e9 cleanup 2026-01-03 15:41:37 +01:00
seydx cfbba5a52c comments 2026-01-02 16:52:55 +01:00
seydx c74a39a30d cleanup 2026-01-02 16:51:23 +01:00
Alex X d4dc670cb5 Add support two-way audio for Dafang camera 2026-01-02 10:19:58 +03:00
seydx 4cff72c9a3 Refactor discovery and session setup logic 2026-01-02 02:22:14 +01:00
seydx f923487546 Improve error logging for video playback 2026-01-01 11:46:17 +01:00
seydx f47a041ece Improve error logging for video playback 2026-01-01 11:37:44 +01:00
Alex X e77210f916 Improve support tutk vendor for xiaomi source 2026-01-01 09:28:02 +03:00
Alex X 44da81774c Add recv/send counters to xiaomi source 2026-01-01 08:42:36 +03:00
seydx 7d4b3fe65b Merge branch 'AlexxIT:master' into wyze 2026-01-01 05:25:58 +01:00
seydx a42ab88dbd add wyze support 2026-01-01 05:24:45 +01:00
Alex X 4dae65a535 Fix audio sample rate for some xiaomi cameras #2006 2025-12-31 17:55:47 +03:00
Alex X a09e1b2f90 Update openapi 2025-12-27 17:50:40 +03:00
Alex X d183b99a44 Update GitHub Pages site link 2025-12-27 15:43:07 +03:00
Alex X 654e78b7c5 Add suggest button to config editor 2025-12-27 12:34:08 +03:00
Alex X 1cd5517026 Log all modules with custom loggers 2025-12-27 12:17:40 +03:00
Alex X 07fb78d661 Merge pull request #1980 from skrashevich/monaco-editor-141225
Replace ace editor to Monaco with config hints
2025-12-27 12:10:15 +03:00
Alex X 86f9f114b5 Code refactoring for #1980 2025-12-27 12:08:39 +03:00
Sergey Krashevich 7e38b4fe89 Add enum and pattern constraint checks to YAML linter
Enhances the YAML linter to validate scalar values against enum and pattern constraints defined in the schema. Adds utility functions for value comparison, schema constraint collection, and applies these checks during linting to provide more precise error messages for invalid values.
2025-12-27 12:00:47 +03:00
Sergey Krashevich 0fd2217bd2 Trigger suggestions for property completions
Adds logic to trigger the suggestion widget when completing a property if it expects a block or has value suggestions, improving the user experience in the config editor.
2025-12-27 10:26:15 +03:00
Sergey Krashevich 76bdc7e065 Refactor block scalar handling in config parser
Simplifies the logic for handling block scalar content by restructuring the conditional checks for blockScalarParentIndent. This improves readability and ensures correct processing of indented lines.
2025-12-27 09:56:52 +03:00
Sergey Krashevich 3bd433c950 Improve null checks and object handling in config.html
Replaces optional chaining with explicit null checks and more robust object property access throughout the file. Refactors destructuring and object spreading to use Object.assign for better compatibility. These changes improve code reliability and compatibility with older browsers or environments lacking support for newer JavaScript features.
2025-12-27 09:09:32 +03:00
Sergey Krashevich 4c50a2c00c Remove redundant min-height property from #config
Deleted an unnecessary 'min-height: 0;' CSS rule from the #config selector, as a fixed min-height is already set.
2025-12-27 08:50:00 +03:00
Sergey Krashevich 0e65bd05c4 Disable word-based suggestions in editor config
Updated the editor configuration in config.html to set wordBasedSuggestions to false and disable word suggestions in the suggestion widget. This change aims to refine the suggestion behavior for a more focused coding experience.
2025-12-27 08:49:26 +03:00
Alex X 05fd0c5cca Merge pull request #1956 from felipecrs/ffmpeg-timeout
Add `#timeout` param for ffmpeg source
2025-12-27 08:38:38 +03:00
Alex X a3a4276c15 Code refactoring for #1956 2025-12-27 08:36:40 +03:00
Sergey Krashevich 0f4607a070 Improve YAML language detection in config.html
Refactored the logic for detecting the YAML language in Monaco editor to handle cases where getLanguages may not be available or Monaco is not loaded. This increases robustness and prevents potential runtime errors.
2025-12-27 08:08:28 +03:00
Sergey Krashevich 11ad1129ed Add hover provider for YAML editor
Introduces a hover provider to the YAML editor using Monaco, displaying property descriptions from the schema when hovering over keys. This enhances the editor's usability by providing inline documentation for YAML properties.
2025-12-27 08:05:42 +03:00
Sergey Krashevich da0b19026c Improve YAML linter with indentation and block scalar checks
Adds detection for inconsistent indentation and unexpected indentation at the document root. Enhances block scalar handling by tracking parent indentation and skipping content lines accordingly. Also improves error reporting for missing map keys or list items.
2025-12-27 07:59:22 +03:00
Alex X c880c37d37 Merge pull request #1992 from FIGIO55/rtsp_onvif_params
Allow rtsp params (like #backchannel=0) from onvif sources
2025-12-26 23:06:30 +03:00
Alex X c4fb66a818 Code refactoring for #1992 2025-12-26 23:05:58 +03:00
Alex X 9b618f45d0 Rewrite webrtc listen logic #1890 #1892 2025-12-26 22:56:39 +03:00
Alex X 3fd1fe2622 Add net interfaces list to webrtc debug 2025-12-26 14:09:22 +03:00
Alex X 2f470fa518 Add support cs2+tcp protocol for xiaomi source 2025-12-26 12:53:52 +03:00
Alex X 212def9ceb Update app dev version format 2025-12-22 17:22:22 +03:00
Alex X 79698365bb Add fetch tags to GitHub actions 2025-12-22 12:24:00 +03:00
Alex X 5b1da84ae2 Add TUTK protocol to xiaomi source 2025-12-22 00:00:33 +03:00
Sergey Krashevich 247d4063ed Merge branch 'monaco-editor-141225' of github.com:skrashevich/go2rtc into monaco-editor-141225 2025-12-21 00:06:29 +03:00
Sergey Krashevich 7ada6d81eb Add YAML schema-based linting and validation
Integrates js-yaml for parsing and adds comprehensive YAML linting with schema-based type checking, duplicate key detection, and improved error reporting. Refactors and extends parsing utilities, enhances completion logic, and introduces a schema tools abstraction for type and property resolution.
2025-12-21 00:02:49 +03:00
Sergey Krashevich eda92afffe Merge branch 'AlexxIT:master' into monaco-editor-141225 2025-12-20 12:54:14 -08:00
Davide Figini ff19885790 Add rtsp params pass through from onvif sources 2025-12-19 15:32:01 +01:00
Davide Figini bbb9466845 Add onvif name and hardware in info field of onvif discovery 2025-12-19 15:12:18 +01:00
Alex X 079d404ed0 Set app version from git info 2025-12-18 19:02:10 +03:00
Alex X 753d6617ab Add support cateye devices to xiaomi source 2025-12-15 21:41:05 +03:00
Alex X dfe47559d1 Update version to 1.9.13 2025-12-14 23:05:24 +03:00
Alex X eabd7d60cd Clean go.sum file 2025-12-14 22:43:18 +03:00
Alex X dfda4b11ff Add about xiaomi source to readme 2025-12-14 22:41:00 +03:00
Alex X 353262307b Update links to icons in resources 2025-12-14 22:35:58 +03:00
Alex X d734140eaf Update reamde 2025-12-14 22:31:13 +03:00
Alex X 2409bb56d7 Move tuya docs to separate page 2025-12-14 21:54:06 +03:00
Alex X df484cc904 Add eseecloud source to readme 2025-12-14 21:45:44 +03:00
Alex X 7e0c7a8173 Merge pull request #1966 from oeiber/master
Update documentation for doorbird devices
2025-12-14 21:29:38 +03:00
Alex X 7eaa4a1b55 Code refactoring for #1966 2025-12-14 21:28:37 +03:00
Alex X 03941a5691 Merge pull request #1977 from edenhaus/preload-list
Add get request to preload endpoint for listing them
2025-12-14 20:20:22 +03:00
Alex X 28821c41e0 Code refactoring for #1977 2025-12-14 20:17:13 +03:00
Alex X 8636e96379 Change ffmpeg transcoder from opus to opus/16000 2025-12-14 17:59:07 +03:00
Alex X 7119384184 Fix backchannel audio for xiaomi chuangmi.camera.72ac1 2025-12-14 17:58:17 +03:00
Alex X 57b0ace802 Add vendor name to xiaomi unsupported vendor message 2025-12-14 17:18:25 +03:00
Alex X b0f46bc919 Fix backchannel audio for xiaomi isa.camera.hlc6 2025-12-14 17:17:52 +03:00
Alex X a4d4598a13 Add support xiaomi source 2025-12-14 13:07:45 +03:00
Sergey Krashevich d041c89c5c schema improvements 2025-12-14 05:20:59 +03:00
Sergey Krashevich ce9138b354 update monaco to 0.55.1 2025-12-14 04:52:35 +03:00
Sergey Krashevich 1e5def35c9 monaco initial 2025-12-14 04:47:27 +03:00
Alex X 17c1f69f66 Update dependencies 2025-12-13 14:25:51 +03:00
Alex X fb31a251b8 Improve fetch for exec source 2025-12-13 14:00:16 +03:00
Alex X c5277daa45 Add UnmarshalHeader func to OPUS codec 2025-12-11 22:01:36 +03:00
Alex X 76a5e160c2 Increase default dial timeout and probe timeout 2025-12-11 22:00:35 +03:00
Alex X 494daed937 Add aliases to PCMA/PCMU codecs 2025-12-11 21:59:57 +03:00
Alex X a86e10446a Add PCML/8000 to ffmpeg transcoder 2025-12-11 21:59:25 +03:00
Robert Resch 209b73a0f1 Add get request to preload endpoint for listing them 2025-12-10 23:30:08 +01:00
oeiber 54473ff1de Update documentation for doorbird devices 2025-12-03 17:04:40 +01:00
Alex X 7eb5fe0355 Add second STUN server from Cloudflare 2025-12-01 14:19:48 +03:00
Alex X fbd5215669 Add custom title to stream page #1957 2025-11-26 11:24:57 +03:00
Alex X 3ebc115cc5 Update title for all WebUI pages 2025-11-26 11:24:57 +03:00
Felipe Santos 31962181cb Add #timeout param for ffmpeg source 2025-11-25 16:37:08 -03:00
Alex X 6d1a95a4e3 Merge pull request #1730 from seydx/tuya-new
New Tuya camera support
2025-11-25 20:36:13 +03:00
Alex X 4ec2849008 Fix WebUI for tuya source 2025-11-25 20:14:00 +03:00
Alex X 4ef6a147a6 Fix panic on login for tuya source 2025-11-25 20:13:45 +03:00
Alex X 94df080bf7 Fix MQTT url for tuya source for some regions 2025-11-25 20:13:22 +03:00
Alex X 86edd814c9 Add support new audio codec for tapo source #1954 2025-11-25 15:51:55 +03:00
Alex X 5a259993d8 Update dependencies 2025-11-22 20:04:05 +03:00
Alex X b6fe8871df Add self-signed cert generator (not used yet) 2025-11-22 20:00:34 +03:00
Alex X b47a2ba73c Add mod_pinggy example 2025-11-22 19:59:46 +03:00
Alex X 4934fa4cc1 Add support tunnels via Pinggy #1853 2025-11-22 19:59:10 +03:00
seydx 61f74820bc Merge branch 'AlexxIT:master' into tuya-new 2025-11-20 21:00:41 +01:00
Alex X 248fc7a11a Remove wrong timeout for http source 2025-11-20 20:03:34 +03:00
Alex X aa0ece2d1e Fix adts producer for VLC player support #1643 2025-11-20 17:52:08 +03:00
Alex X 68036b68c1 Fix timestamp processing for HTTP-FLV 2025-11-19 12:58:23 +03:00
seydx 319dbf2c63 Refactor after merge 2025-11-19 00:10:13 +01:00
seydx 4b419309a8 Merge branch 'master' of https://github.com/AlexxIT/go2rtc into tuya-new 2025-11-19 00:09:34 +01:00
Alex X 42e7a03534 Add HomeKit QR code to WebUI #1138 by @mnakada 2025-11-18 20:55:17 +03:00
Alex X 290e8fcfda Add custom category_id for HomeKit server #985 by @Minims 2025-11-18 20:52:32 +03:00
Alex X 6c78b5cb53 Add 404 error for homekit API request 2025-11-18 20:40:30 +03:00
Alex X c72b205d87 Fix panic for HomeKit server proxy mode #1940 2025-11-18 20:38:04 +03:00
Alex X 2cd009646a Remove homekit source params from streams info API 2025-11-18 19:17:26 +03:00
Alex X 8f5fce4d73 Add support HTTP-FLV with H265 from new Reolink cameras #1938 2025-11-18 16:05:51 +03:00
Alex X b705cadc04 Merge pull request #1942 from sethyx/fix/network-url
Fix link to per-stream net/node graphs
2025-11-18 11:17:04 +03:00
Gergely Szell 2523a5ac38 Fix link to per-stream net/node graphs 2025-11-18 09:12:38 +01:00
Alex X e246e2e756 Fix WebUI for Hass black theme 2025-11-17 12:21:45 +03:00
Alex X 2dc0d58ba7 Update version to 1.9.12 2025-11-16 19:08:34 +03:00
Alex X cb22ae7833 Add security notes to readme 2025-11-16 19:07:03 +03:00
Alex X c98b0a83c4 Merge pull request #1939 from edenhaus/supportedSchemas
Add api endpoint to return supported schemas
2025-11-16 19:04:24 +03:00
Alex X 0bae158e41 Code refactoring for #1939 2025-11-16 19:03:19 +03:00
Robert Resch e2b63a4f6c Remove duplicate code 2025-11-16 16:40:04 +01:00
Robert Resch 3897f10a4d Add api endpoint to return supported schema 2025-11-16 16:33:09 +01:00
Alex X ac80f1470e Add errors output to streams API 2025-11-16 18:20:53 +03:00
Alex X 1fe602679e Update WebUI design 2025-11-15 21:08:00 +03:00
Alex X e2c7d06730 Add check for insecure uri from onvif source 2025-11-11 17:34:01 +03:00
Alex X 2133f5323c Add insecure sources logic 2025-11-11 17:33:15 +03:00
Alex X c10a06d199 Fix wrong log message for streams module 2025-11-11 17:29:10 +03:00
Alex X d053d88ce9 Add support maxwidth/maxheight settings for homekit source 2025-11-11 15:48:12 +03:00
Alex X 2ce38b4486 Add trace log for ignored api paths 2025-11-11 15:43:49 +03:00
Alex X 44d59b1696 Add config local_auth for api module 2025-11-11 15:22:28 +03:00
Alex X 15ec995ecc Add config for the list of modules to init 2025-11-11 15:10:24 +03:00
Alex X 231cab36b2 Add config allow_paths for api module 2025-11-11 15:01:27 +03:00
Alex X 640db3029e Add config allow_paths for exec module 2025-11-11 15:00:58 +03:00
Alex X 2836fdae13 Add config allow_paths for echo module 2025-11-11 14:59:05 +03:00
Alex X 964bb225fa Add support custom params for hass source 2025-11-11 10:54:13 +03:00
Alex X 5cc32197b8 Fix HomeKit proxy for hass source 2025-11-10 15:35:07 +03:00
Alex X bc1a4ac8e4 Fix API /api/homekit/accessories 2025-11-10 15:34:39 +03:00
Alex X 158f9d3a08 Code refactoring for HomeKit server 2025-11-09 21:58:44 +03:00
Alex X 81cfcf877a Fix HomeKit proxy EVENTs 2025-11-09 21:28:53 +03:00
Alex X 96919bf9e3 Add support uint64 to tlv8 2025-11-09 21:25:23 +03:00
Alex X e4359ac217 Rename HomeKit structures according to specs 2025-11-09 21:24:47 +03:00
seydx 56d7a6fee4 Add comments and improve repackaging 2025-10-28 14:54:54 +01:00
seydx 292b32af99 Optimize audio frame size handling in AddTrack to reduce latency for Tuya cameras 2025-10-28 13:53:42 +01:00
seydx 33e4527042 Revert repackaging for backchannel 2025-10-28 12:58:00 +01:00
seydx 62a9046f01 Revert configurable packet size for RepackG711 2025-10-28 12:50:36 +01:00
seydx 25e7ac531e Cleanup and update comments 2025-10-28 11:12:44 +01:00
seydx 0f27bb1124 Update SendOffer to include token and fix mqtt close 2025-10-27 23:57:54 +01:00
seydx 0ff3bf67e1 Comment out MQTT speaker message sending in AddTrack function 2025-10-27 23:57:14 +01:00
seydx 6d3d45e337 Handle speaker messages 2025-10-27 21:48:44 +01:00
seydx c6940eb0f3 Refactor AddTrack to use GetSenderTrack and improve audio handling 2025-10-27 21:48:35 +01:00
seydx 8142d2fc43 Refactor RepackG711 to use configurable packet size 2025-10-27 21:48:15 +01:00
seydx 721ed98afb webrtc: export GetSenderTrack 2025-10-27 21:47:52 +01:00
seydx fb8c6e1b1b Update comments 2025-10-26 22:21:56 +01:00
seydx 80ab32379c Merge branch 'AlexxIT:master' into tuya-new 2025-10-26 16:57:54 +01:00
seydx 863174839c Fix video/audio ssrc and low power cameras 2025-10-26 16:39:59 +01:00
Alex X ff18283d11 Improve homekit secure conn buffers 2025-10-26 15:46:11 +03:00
Alex X 994e0dc526 Improve homekit tlv8 parsing 2025-10-26 15:46:11 +03:00
Felipe Santos d16d260679 Use relative links (git tag-aware) 2025-10-24 12:22:41 -03:00
Felipe Santos d4190290b7 Change back to "when" 2025-10-24 12:20:54 -03:00
Felipe Santos 5660311bb2 Merge branch 'master' of https://github.com/AlexxIT/go2rtc into update-readme 2025-10-24 12:18:30 -03:00
Alex X 7254bd4fbc Code refactoring for tapo source 2025-10-24 17:54:55 +03:00
Alex X 9f407a754d Fix tapo source for some cameras #1918 2025-10-24 17:54:37 +03:00
Alex X cc97bc33c4 Restore simple onvif client logic 2025-10-24 17:28:49 +03:00
seydx bd8e4fa298 Merge branch 'AlexxIT:master' into tuya-new 2025-10-24 13:10:12 +02:00
Alex X 6db4dda535 Fix onvif client for some cameras 2025-10-23 14:42:38 +03:00
Alex X be80eb1ac9 Update version to 1.9.11 2025-10-21 15:32:37 +03:00
Alex X a8d2312cb0 Update dependencies 2025-10-21 15:22:46 +03:00
Alex X 5bbc2aaf2e Move ngrok module docs to another page. 2025-10-21 15:21:33 +03:00
seydx 7abc963a50 Merge branch 'AlexxIT:master' into tuya-new 2025-10-17 03:15:43 +02:00
Alex X f8c88cfbe0 Merge pull request #1641 from felipecrs/fix-build.sh
Improve build.sh
2025-10-15 18:08:09 +03:00
Alex X cf4acd5a8d Code refactoring for #1641 2025-10-15 18:07:18 +03:00
Felipe Santos 19226df7d6 Add go2rtc.exe to gitignore 2025-10-15 09:44:20 -03:00
Felipe Santos c415c8f2af Remove GOTOOLCHAIN like done in CI 2025-10-15 09:44:11 -03:00
Felipe Santos 2751f41afb Merge branch 'master' of https://github.com/AlexxIT/go2rtc into fix-build.sh 2025-10-15 09:37:27 -03:00
Alex X d59cb99f0d Support RTSP redirects #1909 by @eddielth 2025-10-15 14:09:07 +03:00
Alex X 007e8dbc75 Add caution note to readme 2025-10-14 17:20:54 +03:00
Alex X dae396a1ed Merge pull request #1910 from felipecrs/go2rtc-gitignore
Add compiled go2rtc to gitignore
2025-10-14 11:15:48 +03:00
Felipe Santos d894483166 Add go2rtc to gitignore 2025-10-13 08:42:06 -03:00
Felipe Santos 4924cf2256 Merge branch 'master' of https://github.com/AlexxIT/go2rtc into update-readme 2025-10-11 15:54:53 -03:00
Alex X 60ef52f44b Merge pull request #1752 from felipecrs/python3.13
Update Python to 3.13 in docker image
2025-10-11 11:00:59 +03:00
Alex X 240e1960f8 Merge branch 'master' into python3.13 2025-10-11 11:00:52 +03:00
Alex X a107d13e61 Merge pull request #1761 from felipecrs/fix-docker-workflow
Fix docker build and push job when running from a fork
2025-10-11 10:49:35 +03:00
Alex X f911936d79 Merge pull request #1823 from hsakoh/feature/addSwitchBotDoorbellSupport
Add Support for SwitchBot VideoDoorbell
2025-10-10 17:22:55 +03:00
Alex X ea23957f2a Code refactoring for #1823 2025-10-10 17:22:18 +03:00
Alex X f1971cec7c Merge pull request #1589 from seydx/onvif-client
fix: ONVIF client
2025-10-10 16:39:36 +03:00
Alex X 7291c03cea Code refactoring for #1589 2025-10-10 16:35:54 +03:00
Alex X fdb3116027 Added checks for corrupted data to the H265 handler 2025-10-10 11:51:53 +03:00
Alex X c87885be48 Merge pull request #1758 from seydx/rtsp-udp
Add RTSP UDP transport support
2025-10-10 11:30:28 +03:00
Alex X 98f88d037e Remove UDP example from readme 2025-10-10 11:11:29 +03:00
Alex X fde1fdc592 Code refactoring for #1758 2025-10-09 21:10:54 +03:00
Alex X cca216ace5 Merge pull request #1744 from seydx/secrets-file
Secrets Management
2025-10-07 15:23:02 +03:00
Alex X e953e949ef Merge branch 'master' into secrets-file 2025-10-07 15:22:44 +03:00
Alex X fe2cc4b525 Code refactoring for #1744 2025-10-07 15:15:04 +03:00
Alex X 6a67fc3712 Merge pull request #1895 from oeiber/master
Fix connection issues in conjunction with doorbird backchannel
2025-10-05 16:01:25 +03:00
Alex X 94b7c33485 Update backchannel.go
Code refactoring for #1895
2025-10-05 16:00:58 +03:00
Oliver Eiber 887f0f4890 fix connection handling in conjunction with doorbird backchannel 2025-10-04 21:37:19 +02:00
Alex X ec08dfee9c Fix stack API for new pion version 2025-10-04 19:19:01 +03:00
Alex X 54b95dced4 Fix probing after #1762 2025-10-04 19:18:36 +03:00
Felipe Santos 56bf53208e Merge branch 'master' of https://github.com/AlexxIT/go2rtc into update-readme 2025-10-01 11:31:34 -03:00
Alex X 37d7409e82 Merge pull request #1762 from seydx/preload
Preload Streams
2025-10-01 17:22:19 +03:00
Alex X 4dd1f73a18 Update readme for #1762 2025-10-01 17:03:32 +03:00
Alex X 22cc8ac2c4 Code refactoring for #1762 2025-10-01 16:57:39 +03:00
seydx a667acad07 Merge branch 'master' of https://github.com/AlexxIT/go2rtc into rtsp-udp 2025-09-30 19:20:21 +02:00
seydx ee5e31d3b3 Merge branch 'master' of https://github.com/AlexxIT/go2rtc into tuya-new 2025-09-30 19:14:43 +02:00
seydx c196b82a72 Merge branch 'master' of https://github.com/AlexxIT/go2rtc into preload 2025-09-30 19:13:36 +02:00
seydx 670370056c Refactor secrets management 2025-09-30 15:35:32 +02:00
seydx 0c5a2bf02b Remove NewSecret function and related import from helpers.go 2025-09-30 15:21:30 +02:00
seydx 8f9e78be0c Merge branch 'master' of https://github.com/AlexxIT/go2rtc into secrets-file 2025-09-30 14:58:40 +02:00
seydx 50d5fa93b6 Set Content-Type header to MimeJSON in ResponsePrettyJSON function 2025-09-30 14:50:57 +02:00
seydx 3a0e4078fd Dont redact hass entry title 2025-09-30 14:48:58 +02:00
Alex X 549da0257e Merge pull request #1745 from seydx/optimize-ring
Optimize ring
2025-09-30 13:36:42 +03:00
Alex X 2b5f9429a8 Update FFmpeg command for encoding H265 (fix profile and level) 2025-09-30 12:17:41 +03:00
Alex X c7119f4403 Fix RTP processing for H265 codec (restore VPS,SPS,PPS) 2025-09-30 12:14:41 +03:00
Alex X 7d9862202a Code refactoring for video-rtc.js 2025-09-30 12:12:29 +03:00
Alex X dd7130d2d4 Merge pull request #1644 from seydx/check-h265
Add support for H.265 codec verification
2025-09-29 18:23:05 +03:00
Alex X d697bdcf05 Code refactoring for #1644 2025-09-29 18:21:36 +03:00
seydx abd61919cf Merge branch 'master' of https://github.com/AlexxIT/go2rtc into secrets-file 2025-09-26 15:15:59 +02:00
seydx ad2383de80 Merge branch 'AlexxIT:master' into check-h265 2025-09-26 15:10:14 +02:00
seydx 9f3ff98951 Merge branch 'AlexxIT:master' into onvif-client 2025-09-26 15:09:11 +02:00
seydx d286980548 Merge branch 'AlexxIT:master' into optimize-ring 2025-09-26 15:09:01 +02:00
seydx 87533ac5a1 Merge branch 'AlexxIT:master' into preload 2025-09-26 15:08:52 +02:00
seydx d05451416d Merge branch 'AlexxIT:master' into rtsp-udp 2025-09-26 15:08:17 +02:00
seydx 5c01cbad9e Merge branch 'AlexxIT:master' into tuya-new 2025-09-26 15:07:52 +02:00
Oliver Eiber f2242e31c8 impove connection timeout to prevent reconnections after 30 seconds 2025-08-19 07:53:10 +02:00
Oliver Eiber 975a43d392 reduce audio delay by lowering buffer size 2025-07-31 21:07:45 +02:00
Oliver Eiber 3d38e5e567 fix unexpected close of backchannel streams 2025-07-30 23:37:06 +02:00
Oliver Eiber 7d2ad92c4b fix app crashes
remove orphaned streams
2025-07-28 22:27:38 +02:00
hsakoh b82023bc32 Add Support for SwitchBot VideoDoorbell 2025-07-26 01:40:27 +09:00
Oliver Eiber a92e04b6e0 added audio mixing capability to avoid device overload when multiple backchannel audio streams are connected 2025-07-22 20:54:24 +02:00
Oliver Eiber 56e61a85ee proper error handling
cleanup files
2025-07-16 21:07:34 +02:00
Felipe Santos 7c3d97b0a2 Remove mentions of RTSPtoWebRTC 2025-07-10 16:28:37 -03:00
Felipe Santos 88a2f33b71 Fix table 2025-07-10 16:19:48 -03:00
Felipe Santos b7557d750e Apply suggestions from code review 2025-07-10 16:16:50 -03:00
Felipe Santos ce4bee3be7 Update README.md 2025-07-10 16:15:42 -03:00
Felipe Santos 5e746e3367 Update README.md 2025-07-10 16:15:14 -03:00
seydx 7d83b0d6c8 Merge branch 'AlexxIT:master' into check-h265 2025-07-10 16:17:06 +02:00
seydx 708277230a Merge branch 'AlexxIT:master' into onvif-client 2025-07-10 16:16:11 +02:00
seydx 06e6e31443 Merge branch 'AlexxIT:master' into optimize-ring 2025-07-10 16:16:02 +02:00
seydx 8474f2f571 Merge branch 'AlexxIT:master' into preload 2025-07-10 16:15:53 +02:00
seydx 9ddea7d9d6 Merge branch 'AlexxIT:master' into rtsp-udp 2025-07-10 16:15:17 +02:00
seydx c2fbd372b3 Merge branch 'AlexxIT:master' into secrets-file 2025-07-10 16:15:07 +02:00
seydx e9611769be deps 2025-07-10 16:12:07 +02:00
seydx c5dfa84ff2 readme 2025-07-10 16:09:18 +02:00
seydx da68101c09 Optimize URL decoding and update MQTT keep-alive 2025-07-10 16:09:18 +02:00
seydx 30c418542c readme 2025-07-10 16:09:18 +02:00
seydx b58c1a7ed6 Update README.md
Co-authored-by: Felipe Santos <felipecassiors@gmail.com>
2025-07-10 16:09:18 +02:00
seydx 47e87281a1 increase timeout 2025-07-10 16:09:18 +02:00
seydx 60b6b93ff8 fix concurrent writes and improve mqtt 2025-07-10 16:09:18 +02:00
seydx 3149b6f750 Update README.md
Co-authored-by: Felipe Santos <felipecassiors@gmail.com>
2025-07-10 16:09:18 +02:00
seydx e44f1ad53e Update README.md
Co-authored-by: Felipe Santos <felipecassiors@gmail.com>
2025-07-10 16:09:18 +02:00
seydx c38c8a7fce readme 2025-07-10 16:09:18 +02:00
seydx 9efc717633 ... 2025-07-10 16:09:18 +02:00
seydx a2d422f5cb format 2025-07-10 16:09:18 +02:00
seydx 3036dd7cfe refactor 2025-07-10 16:09:18 +02:00
seydx 5be5d9247c refactor, simplify api, add support for email/password auth 2025-07-10 16:09:18 +02:00
seydx 42b7eea852 fix: correct transceiver direction check in getSender method 2025-07-10 16:09:18 +02:00
seydx 7ee3f6e4f7 Update README.md
Co-authored-by: Felipe Santos <felipecassiors@gmail.com>
2025-07-10 16:09:18 +02:00
seydx fbd8d995ed refactor and increase timeout 2025-07-10 16:09:18 +02:00
seydx 998c85d6f5 - support adding cameras via interface
- support qr code auth
- support resolution change
- support h265
- refactor code
2025-07-10 16:09:18 +02:00
seydx 67dfc942a0 update useful links 2025-07-10 16:09:18 +02:00
seydx 1cc8b373de change query 2025-07-10 16:09:18 +02:00
seydx f045f3fccd dont expose raw url in stream info 2025-07-10 16:09:18 +02:00
seydx 691f6d9cdd update README 2025-07-10 16:09:18 +02:00
seydx 8a8fb66eeb Update README.md
Co-authored-by: Felipe Santos <felipecassiors@gmail.com>
2025-07-10 16:09:18 +02:00
seydx e7044a93f6 fix video/audio and minor improvements 2025-07-10 16:09:18 +02:00
seydx a9bcb46f38 refactor 2025-07-10 16:09:18 +02:00
seydx 16a812c8b8 revert webrtc 2025-07-10 16:09:18 +02:00
seydx 27fe2622ec revert demuxer 2025-07-10 16:09:18 +02:00
seydx 3d222136f9 support h265 2025-07-10 16:09:18 +02:00
seydx 524cdb7176 demuxer: support hvc and pcm 2025-07-10 16:09:18 +02:00
seydx 499dc10390 comments 2025-07-10 16:09:18 +02:00
seydx e7bd3d401f wip h265 datachannel 2025-07-10 16:09:18 +02:00
seydx 5ec942cb5e fix stream mode 2025-07-10 16:09:18 +02:00
seydx 6c255cd2f2 fix two way audio 2025-07-10 16:09:18 +02:00
seydx 4f969d750a readme 2025-07-10 16:09:18 +02:00
seydx bd2cbe20e0 add useful links 2025-07-10 16:09:18 +02:00
seydx 6d8d6a91ef format 2025-07-10 16:09:18 +02:00
seydx 05c12b34e5 refactor 2025-07-10 16:09:18 +02:00
seydx 43b7a662c1 use streamType parameter 2025-07-10 16:09:18 +02:00
seydx a7e76db464 change hls url and query and add more checks 2025-07-10 16:09:18 +02:00
seydx b797a2fcd1 add HLS support and fix skill response 2025-07-10 16:09:18 +02:00
seydx 6e35f1a389 add rtsp support 2025-07-10 16:09:18 +02:00
seydx 30d48e139c implement skill handling and media configuration 2025-07-10 16:09:18 +02:00
seydx e74fc6f198 add tuya source 2025-07-10 16:09:18 +02:00
Felipe Santos 6734492a2d Change TIPS to Tips 2025-07-07 14:31:51 -03:00
Felipe Santos 90217d61d0 Restore ToC 2025-07-07 14:25:55 -03:00
Felipe Santos ba27a7f68c Merge branch 'master' of https://github.com/AlexxIT/go2rtc into update-readme 2025-07-07 14:22:28 -03:00
Oliver Eiber e00d211619 ensure that doorbird errors where shown in logs 2025-07-06 22:33:25 +02:00
Oliver Eiber c68e3cafe4 fixes doorbird backchannel audio:
- proper session handling
- honor http status codes
- prevent device from being flooded by limiting concurrent audio channels
2025-07-03 23:35:58 +02:00
seydx 7bb0f0d2e6 readme 2025-06-19 10:29:56 +02:00
seydx 96d7066085 Merge branch 'AlexxIT:master' into secrets-file 2025-06-16 10:05:59 +02:00
seydx ae49946740 Merge branch 'AlexxIT:master' into optimize-ring 2025-06-16 10:05:31 +02:00
seydx fcc91c9b8a Merge branch 'AlexxIT:master' into onvif-client 2025-06-16 10:05:22 +02:00
seydx 994fd41826 Merge branch 'AlexxIT:master' into check-h265 2025-06-16 10:04:28 +02:00
seydx 3282b38900 Merge branch 'AlexxIT:master' into rtsp-udp 2025-06-16 10:03:19 +02:00
seydx 647b2acf48 cleanup 2025-06-16 09:58:55 +02:00
seydx ef318f663e fix preload queries 2025-06-16 09:32:07 +02:00
seydx 5771454400 use preload as format name 2025-06-16 00:50:48 +02:00
seydx 6732e726d5 update preload consumer to handle RTP packets 2025-06-16 00:33:16 +02:00
seydx 45503aa8c5 Merge branch 'AlexxIT:master' into preload 2025-06-14 00:40:06 +02:00
seydx b6579122d1 fix 2025-06-06 03:11:28 +02:00
seydx 42a67f8ad5 comments 2025-06-06 02:18:00 +02:00
seydx 91eeefec68 openapi: add preload endpoints 2025-06-05 16:01:49 +03:00
seydx 8ab7aeb8b2 update readme 2025-06-05 15:51:14 +03:00
seydx 493fa1ef6a add api endpoints and change config syntax 2025-06-05 11:33:03 +03:00
seydx 020549ef60 readme 2025-06-02 22:16:43 +03:00
seydx dfc1f45f97 support preloading streams 2025-06-02 22:06:47 +03:00
Felipe Santos 641e65ee95 Fix docker build and push job when running from a fork 2025-06-02 13:21:54 -03:00
seydx 24ca87e00d dont fallback to tcp if udp failes 2025-06-01 18:40:53 +03:00
seydx 859cd1cbe6 support rtsp udp transport 2025-06-01 01:44:01 +03:00
seydx 79656d1344 update readme 2025-05-26 23:10:57 +02:00
seydx 759f979182 dont redact config.env values 2025-05-26 22:23:24 +02:00
seydx 7c17e64090 format 2025-05-26 22:21:33 +02:00
seydx bf45f64a7e - refactor secrets
- add support for env in config
- redact sensitive information in logs/responses
2025-05-26 21:56:45 +02:00
Felipe Santos 72890d5983 Update Python to 3.13 in docker image 2025-05-26 14:01:26 -03:00
seydx e8e798d955 Merge branch 'AlexxIT:master' into secrets-file 2025-05-23 08:02:17 +02:00
seydx 8a8b379bfc Merge branch 'AlexxIT:master' into optimize-ring 2025-05-23 08:00:40 +02:00
seydx ca491def83 Merge branch 'AlexxIT:master' into onvif-client 2025-05-23 08:00:28 +02:00
seydx 5a597277a9 Merge branch 'AlexxIT:master' into check-h265 2025-05-23 07:59:11 +02:00
Felipe Santos 6f2afdc97b Remove it from schema.json too 2025-05-22 11:16:54 -03:00
Felipe Santos 6e2e0d353a Revert Home Assistant config dir to /config in example 2025-05-22 11:14:56 -03:00
seydx c90fcd1ce1 refactor 2025-05-21 13:16:49 +02:00
seydx e0687db9e2 add template parsing 2025-05-20 23:07:04 +02:00
seydx 24310e2f7a remove parse 2025-05-20 22:44:07 +02:00
seydx a1f0b86ab3 format 2025-05-20 22:29:27 +02:00
seydx 7f87c6e478 refactor 2025-05-20 21:40:33 +02:00
seydx a0145b4b24 revert handlers 2025-05-20 15:52:26 +02:00
seydx 2fcbb1d836 refactor 2025-05-20 15:51:15 +02:00
seydx a2beea1bbd refactor 2025-05-20 13:59:46 +02:00
seydx e5e55b7a50 improve secret vars and parse url with secrets 2025-05-20 13:05:11 +02:00
seydx 0830d8342e add secret management functions 2025-05-20 12:07:46 +02:00
seydx adb1b21e81 format 2025-05-17 16:37:12 +02:00
Felipe Santos 4788011249 Fix typo 2025-05-12 19:51:31 -03:00
seydx edfa09bb9f ring: update peer connection state handling and pass sdo to producer 2025-05-10 19:04:47 +02:00
seydx 2eef7bdbd3 ring: implement session management and caching 2025-05-08 17:38:09 +02:00
seydx 124556f4db ring: skip refetching cameras to increase loading speed and refactor ring url 2025-05-08 16:09:04 +02:00
Felipe Santos f1faa95897 Fix some links 2025-05-03 13:12:50 -03:00
Felipe Santos b84df84e53 Update README 2025-05-03 13:06:28 -03:00
seydx d528e167db Merge branch 'AlexxIT:master' into onvif-client 2025-05-02 17:56:08 +02:00
seydx f151969fe1 Merge branch 'AlexxIT:master' into check-h265 2025-05-02 17:55:23 +02:00
seydx 251686608a Merge branch 'AlexxIT:master' into onvif-client 2025-04-30 22:55:28 +02:00
seydx 993aa613fd Merge branch 'AlexxIT:master' into check-h265 2025-04-30 22:54:47 +02:00
seydx 2fdfec6f21 Merge branch 'AlexxIT:master' into onvif-client 2025-04-26 00:24:59 +02:00
seydx a0d8f3ae81 Merge branch 'AlexxIT:master' into check-h265 2025-04-26 00:24:18 +02:00
seydx fd5746a954 Merge branch 'AlexxIT:master' into onvif-client 2025-04-22 12:30:30 +02:00
seydx 2c3813deb9 Merge branch 'AlexxIT:master' into check-h265 2025-04-22 12:29:49 +02:00
seydx a8b51ad619 Merge branch 'AlexxIT:master' into onvif-client 2025-04-09 17:10:07 +02:00
seydx 49c4d45731 Merge branch 'AlexxIT:master' into check-h265 2025-04-09 17:09:19 +02:00
seydx 1282b23c57 Merge branch 'AlexxIT:master' into onvif-client 2025-04-01 22:12:55 +02:00
seydx 54afd0b50b Merge branch 'AlexxIT:master' into check-h265 2025-04-01 22:11:46 +02:00
seydx 6ee748a87a Merge branch 'AlexxIT:master' into onvif-client 2025-03-23 11:08:56 +01:00
seydx 0ebda76cc8 Merge branch 'AlexxIT:master' into check-h265 2025-03-23 11:08:06 +01:00
seydx 68b3dc6f37 Merge branch 'AlexxIT:master' into onvif-client 2025-03-22 14:56:10 +01:00
seydx 3c83102e91 Merge branch 'AlexxIT:master' into check-h265 2025-03-22 14:55:26 +01:00
seydx dd77c3e1f0 Merge branch 'AlexxIT:master' into onvif-client 2025-03-18 13:16:31 +01:00
seydx e5e1f6bd05 Merge branch 'AlexxIT:master' into check-h265 2025-03-18 13:15:45 +01:00
seydx ac96b64c64 change codec priority handling for h265 2025-03-16 14:16:05 +01:00
seydx fa8fd60ac6 Merge branch 'AlexxIT:master' into onvif-client 2025-03-13 14:36:54 +01:00
seydx 9c9393e0cf Merge branch 'AlexxIT:master' into check-h265 2025-03-13 14:35:57 +01:00
seydx 8b26f9309f Merge branch 'AlexxIT:master' into onvif-client 2025-03-12 22:19:50 +01:00
seydx 4027809f32 Merge branch 'AlexxIT:master' into check-h265 2025-03-12 22:19:17 +01:00
seydx b28ffa9543 indentation 2025-03-11 01:52:16 +01:00
seydx 7836f2e47f check h265 2025-03-11 01:50:41 +01:00
seydx 648873978c Merge branch 'AlexxIT:master' into onvif-client 2025-03-11 01:45:25 +01:00
Felipe Santos c51c13b4b8 Avoid export pollution 2025-03-10 19:49:04 -03:00
Felipe Santos 9a7c7d2a4b Fix GOTOOLCHAIN in build.sh 2025-03-10 19:45:13 -03:00
Felipe Santos 5f17474ff4 Fix build in linux amd64 2025-03-10 19:36:16 -03:00
Felipe Santos 3376bf8b99 Avoid ignoring errors 2025-03-10 19:34:18 -03:00
Felipe Santos ad1bea088e Fix check_command in build.sh 2025-03-10 19:18:04 -03:00
seydx d0ac99fc69 fix onvif client 2025-02-10 20:21:25 +01:00
374 changed files with 35557 additions and 6474 deletions
+6 -32
View File
@@ -4,7 +4,8 @@ on:
workflow_dispatch:
push:
branches:
- 'master'
- 'beta'
- 'dev'
tags:
- 'v*'
@@ -19,7 +20,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with: { go-version: '1.25' }
with: { go-version: '1.26' }
- name: Build go2rtc_win64
env: { GOOS: windows, GOARCH: amd64 }
@@ -123,9 +124,7 @@ jobs:
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ github.repository }}
ghcr.io/${{ github.repository }}
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}},enable=false
@@ -137,13 +136,6 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
@@ -180,9 +172,7 @@ jobs:
id: meta-hw
uses: docker/metadata-action@v5
with:
images: |
${{ github.repository }}
ghcr.io/${{ github.repository }}
images: ghcr.io/${{ github.repository }}
flavor: |
suffix=-hardware,onlatest=true
latest=auto
@@ -197,13 +187,6 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
@@ -235,9 +218,7 @@ jobs:
id: meta-rk
uses: docker/metadata-action@v5
with:
images: |
${{ github.repository }}
ghcr.io/${{ github.repository }}
images: ghcr.io/${{ github.repository }}
flavor: |
suffix=-rockchip,onlatest=true
latest=auto
@@ -252,13 +233,6 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
+24 -8
View File
@@ -17,21 +17,37 @@ concurrency:
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
package-manager-cache: false
- name: Install dependencies
run: npm install --no-package-lock
- name: Build docs
run: npm run docs:build
- name: Copy docs into website
run: rsync -a --exclude '.vitepress/' --exclude 'README.md' website/ website/.vitepress/dist/
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: website/.vitepress/dist
# Single deploy job since we're just deploying
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: './website'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.26'
- name: Build Go binary
run: go build -ldflags "-s -w" -trimpath -o ./go2rtc
+15
View File
@@ -9,6 +9,21 @@ go2rtc_linux*
go2rtc_mac*
go2rtc_win*
/go2rtc
/go2rtc.exe
0_test.go
.DS_Store
website/.vitepress/cache
website/.vitepress/dist
node_modules
package-lock.json
CLAUDE.md
*/**/CLAUDE.md
.claude*
.ruff*
.omc
+506 -1318
View File
File diff suppressed because it is too large Load Diff
-117
View File
@@ -1,117 +0,0 @@
# API
Fill free to make any API design proposals.
## HTTP API
Interactive [OpenAPI](https://alexxit.github.io/go2rtc/api/).
`www/stream.html` - universal viewer with support params in URL:
- multiple streams on page `src=camera1&src=camera2...`
- stream technology autoselection `mode=webrtc,webrtc/tcp,mse,hls,mp4,mjpeg`
- stream technology comparison `src=camera1&mode=webrtc&mode=mse&mode=mp4`
- player width setting in pixels `width=320px` or percents `width=50%`
`www/webrtc.html` - WebRTC viewer with support two way audio and params in URL:
- `media=video+audio` - simple viewer
- `media=video+audio+microphone` - two way audio from camera
- `media=camera+microphone` - stream from browser
- `media=display+speaker` - stream from desktop
## JavaScript API
- You can write your viewer from the scratch
- You can extend the built-in viewer - `www/video-rtc.js`
- Check example - `www/video-stream.js`
- Check example - https://github.com/AlexxIT/WebRTC
`video-rtc.js` features:
- support technologies:
- WebRTC over UDP or TCP
- MSE or HLS or MP4 or MJPEG over WebSocket
- automatic selection best technology according on:
- codecs inside your stream
- current browser capabilities
- current network configuration
- automatic stop stream while browser or page not active
- automatic stop stream while player not inside page viewport
- automatic reconnection
Technology selection based on priorities:
1. Video and Audio better than just Video
2. H265 better than H264
3. WebRTC better than MSE, than HLS, than MJPEG
## WebSocket API
Endpoint: `/api/ws`
Query parameters:
- `src` (required) - Stream name
### WebRTC
Request SDP:
```json
{"type":"webrtc/offer","value":"v=0\r\n..."}
```
Response SDP:
```json
{"type":"webrtc/answer","value":"v=0\r\n..."}
```
Request/response candidate:
- empty value also allowed and optional
```json
{"type":"webrtc/candidate","value":"candidate:3277516026 1 udp 2130706431 192.168.1.123 54321 typ host"}
```
### MSE
Request:
- codecs list optional
```json
{"type":"mse","value":"avc1.640029,avc1.64002A,avc1.640033,hvc1.1.6.L153.B0,mp4a.40.2,mp4a.40.5,flac,opus"}
```
Response:
```json
{"type":"mse","value":"video/mp4; codecs=\"avc1.64001F,mp4a.40.2\""}
```
### HLS
Request:
```json
{"type":"hls","value":"avc1.640029,avc1.64002A,avc1.640033,hvc1.1.6.L153.B0,mp4a.40.2,mp4a.40.5,flac"}
```
Response:
- you MUST rewrite full HTTP path to `http://192.168.1.123:1984/api/hls/playlist.m3u8`
```json
{"type":"hls","value":"#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1000000,CODECS=\"avc1.64001F,mp4a.40.2\"\nhls/playlist.m3u8?id=DvmHdd9w"}
```
### MJPEG
Request/response:
```json
{"type":"mjpeg"}
```
-537
View File
@@ -1,537 +0,0 @@
openapi: 3.1.0
info:
title: go2rtc
license: { name: MIT,url: https://opensource.org/licenses/MIT }
version: 1.0.0
contact: { url: https://github.com/AlexxIT/go2rtc }
description: |
Ultimate camera streaming application with support RTSP, RTMP, HTTP-FLV, WebRTC, MSE, HLS, MP4, MJPEG, HomeKit, FFmpeg, etc.
servers:
- url: http://localhost:1984
components:
parameters:
stream_src_path:
name: src
in: path
description: Source stream name
required: true
schema: { type: string }
example: camera1
stream_dst_path:
name: dst
in: path
description: Destination stream name
required: true
schema: { type: string }
example: camera1
stream_src_query:
name: src
in: query
description: Source stream name
required: true
schema: { type: string }
example: camera1
mp4_filter:
name: mp4
in: query
description: MP4 codecs filter
required: false
schema:
type: string
enum: [ "", flac, all ]
example: flac
video_filter:
name: video
in: query
description: Video codecs filter
schema:
type: string
enum: [ "", all, h264, h265, mjpeg ]
example: h264,h265
audio_filter:
name: audio
in: query
description: Audio codecs filter
schema:
type: string
enum: [ "", all, aac, opus, pcm, pcmu, pcma ]
example: aac
responses:
discovery:
description: ""
content:
application/json:
example: { streams: [ { "name": "Camera 1","url": "..." } ] }
webtorrent:
description: ""
content:
application/json:
example: { share: AKDypPy4zz, pwd: H0Km1HLTTP }
tags:
- name: Application
description: "[Module: API](https://github.com/AlexxIT/go2rtc#module-api)"
- name: Config
description: "[Configuration](https://github.com/AlexxIT/go2rtc#configuration)"
- name: Streams list
description: "[Module: Streams](https://github.com/AlexxIT/go2rtc#module-streams)"
- name: Consume stream
- name: Snapshot
- name: Produce stream
- name: Discovery
- name: ONVIF
- name: RTSPtoWebRTC
- name: WebTorrent
description: "[Module: WebTorrent](https://github.com/AlexxIT/go2rtc#module-webtorrent)"
- name: Debug
paths:
/api:
get:
summary: Get application info
tags: [ Application ]
responses:
"200":
description: ""
content:
application/json:
example: { config_path: "/config/go2rtc.yaml",host: "192.168.1.123:1984",rtsp: { listen: ":8554",default_query: "video&audio" },version: "1.5.0" }
/api/exit:
post:
summary: Close application
tags: [ Application ]
parameters:
- name: code
in: query
description: Application exit code
required: false
schema: { type: integer }
example: 100
responses:
default:
description: Default response
/api/restart:
post:
summary: Restart Daemon
description: Restarts the daemon.
tags: [ Application ]
responses:
default:
description: Default response
/api/config:
get:
summary: Get main config file content
tags: [ Config ]
responses:
"200":
description: ""
content:
application/yaml: { example: "streams:..." }
post:
summary: Rewrite main config file
tags: [ Config ]
requestBody:
content:
"*/*": { example: "streams:..." }
responses:
default:
description: Default response
patch:
summary: Merge changes to main config file
tags: [ Config ]
requestBody:
content:
"*/*": { example: "streams:..." }
responses:
default:
description: Default response
/api/streams:
get:
summary: Get all streams info
tags: [ Streams list ]
responses:
"200":
description: ""
content:
application/json: { example: { camera1: { producers: [ ],consumers: [ ] } } }
put:
summary: Create new stream
tags: [ Streams list ]
parameters:
- name: src
in: query
description: Stream source (URI)
required: true
schema: { type: string }
example: "rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0"
- name: name
in: query
description: Stream name
required: false
schema: { type: string }
example: camera1
responses:
default:
description: Default response
patch:
summary: Update stream source
tags: [ Streams list ]
parameters:
- name: src
in: query
description: Stream source (URI)
required: true
schema: { type: string }
example: "rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0"
- name: name
in: query
description: Stream name
required: true
schema: { type: string }
example: camera1
responses:
default:
description: Default response
delete:
summary: Delete stream
tags: [ Streams list ]
parameters:
- name: src
in: query
description: Stream name
required: true
schema: { type: string }
example: camera1
responses:
default:
description: Default response
post:
summary: Send stream from source to destination
description: "[Stream to camera](https://github.com/AlexxIT/go2rtc#stream-to-camera)"
tags: [ Streams list ]
parameters:
- name: src
in: query
description: Stream source (URI)
required: true
schema: { type: string }
example: "ffmpeg:http://example.com/song.mp3#audio=pcma#input=file"
- name: dst
in: query
description: Destination stream name
required: true
schema: { type: string }
example: camera1
responses:
default:
description: Default response
/api/streams?src={src}:
get:
summary: Get stream info in JSON format
tags: [ Consume stream ]
parameters:
- $ref: "#/components/parameters/stream_src_path"
responses:
"200":
description: ""
content:
application/json:
example: { producers: [ { url: "rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0" } ], consumers: [ ] }
/api/webrtc?src={src}:
post:
summary: Get stream in WebRTC format (WHEP)
description: "[Module: WebRTC](https://github.com/AlexxIT/go2rtc#module-webrtc)"
tags: [ Consume stream ]
parameters:
- $ref: "#/components/parameters/stream_src_path"
requestBody:
description: |
Support:
- JSON format (`Content-Type: application/json`)
- WHEP standard (`Content-Type: application/sdp`)
- raw SDP (`Content-Type: anything`)
required: true
content:
application/json: { example: { type: offer, sdp: "v=0..." } }
"application/sdp": { example: "v=0..." }
"*/*": { example: "v=0..." }
responses:
"200":
description: "Response on JSON or raw SDP"
content:
application/json: { example: { type: answer, sdp: "v=0..." } }
application/sdp: { example: "v=0..." }
"201":
description: "Response on `Content-Type: application/sdp`"
content:
application/sdp: { example: "v=0..." }
/api/stream.mp4?src={src}:
get:
summary: Get stream in MP4 format (HTTP progressive)
description: "[Module: MP4](https://github.com/AlexxIT/go2rtc#module-mp4)"
tags: [ Consume stream ]
parameters:
- $ref: "#/components/parameters/stream_src_path"
- name: duration
in: query
description: Limit the length of the stream in seconds
required: false
schema: { type: string }
example: 15
- name: filename
in: query
description: Download as a file with this name
required: false
schema: { type: string }
example: camera1.mp4
- $ref: "#/components/parameters/mp4_filter"
- $ref: "#/components/parameters/video_filter"
- $ref: "#/components/parameters/audio_filter"
responses:
200:
description: ""
content: { video/mp4: { example: "" } }
/api/stream.m3u8?src={src}:
get:
summary: Get stream in HLS format
description: "[Module: HLS](https://github.com/AlexxIT/go2rtc#module-hls)"
tags: [ Consume stream ]
parameters:
- $ref: "#/components/parameters/stream_src_path"
- $ref: "#/components/parameters/mp4_filter"
- $ref: "#/components/parameters/video_filter"
- $ref: "#/components/parameters/audio_filter"
responses:
200:
description: ""
content: { application/vnd.apple.mpegurl: { example: "" } }
/api/stream.mjpeg?src={src}:
get:
summary: Get stream in MJPEG format
description: "[Module: MJPEG](https://github.com/AlexxIT/go2rtc#module-mjpeg)"
tags: [ Consume stream ]
parameters:
- $ref: "#/components/parameters/stream_src_path"
responses:
200:
description: ""
content: { multipart/x-mixed-replace: { example: "" } }
/api/frame.jpeg?src={src}:
get:
summary: Get snapshot in JPEG format
description: "[Module: MJPEG](https://github.com/AlexxIT/go2rtc#module-mjpeg)"
tags: [ Snapshot ]
parameters:
- $ref: "#/components/parameters/stream_src_path"
responses:
200:
description: ""
content: { image/jpeg: { example: "" } }
/api/frame.mp4?src={src}:
get:
summary: Get snapshot in MP4 format
description: "[Module: MP4](https://github.com/AlexxIT/go2rtc#module-mp4)"
tags: [ Snapshot ]
parameters:
- $ref: "#/components/parameters/stream_src_path"
responses:
200:
description: ""
content: { video/mp4: { example: "" } }
/api/webrtc?dst={dst}:
post:
summary: Post stream in WebRTC format
description: "[Incoming: WebRTC/WHIP](https://github.com/AlexxIT/go2rtc#incoming-webrtcwhip)"
tags: [ Produce stream ]
parameters:
- $ref: "#/components/parameters/stream_dst_path"
responses:
default:
description: Default response
/api/stream.flv?dst={dst}:
post:
summary: Post stream in FLV format
description: "[Incoming sources](https://github.com/AlexxIT/go2rtc#incoming-sources)"
tags: [ Produce stream ]
parameters:
- $ref: "#/components/parameters/stream_dst_path"
responses:
default:
description: Default response
/api/stream.ts?dst={dst}:
post:
summary: Post stream in MPEG-TS format
description: "[Incoming sources](https://github.com/AlexxIT/go2rtc#incoming-sources)"
tags: [ Produce stream ]
parameters:
- $ref: "#/components/parameters/stream_dst_path"
responses:
default:
description: Default response
/api/stream.mjpeg?dst={dst}:
post:
summary: Post stream in MJPEG format
description: "[Incoming sources](https://github.com/AlexxIT/go2rtc#incoming-sources)"
tags: [ Produce stream ]
parameters:
- $ref: "#/components/parameters/stream_dst_path"
responses:
default:
description: Default response
/api/dvrip:
get:
summary: DVRIP cameras discovery
description: "[Source: DVRIP](https://github.com/AlexxIT/go2rtc#source-dvrip)"
tags: [ Discovery ]
responses:
default:
description: Default response
/api/ffmpeg/devices:
get:
summary: FFmpeg USB devices discovery
description: "[Source: FFmpeg Device](https://github.com/AlexxIT/go2rtc#source-ffmpeg-device)"
tags: [ Discovery ]
responses:
default:
description: Default response
/api/ffmpeg/hardware:
get:
summary: FFmpeg hardware transcoding discovery
description: "[Hardware acceleration](https://github.com/AlexxIT/go2rtc/wiki/Hardware-acceleration)"
tags: [ Discovery ]
responses:
default:
description: Default response
/api/hass:
get:
summary: Home Assistant cameras discovery
description: "[Source: Hass](https://github.com/AlexxIT/go2rtc#source-hass)"
tags: [ Discovery ]
responses:
default:
description: Default response
/api/homekit:
get:
summary: HomeKit cameras discovery
description: "[Source: HomeKit](https://github.com/AlexxIT/go2rtc#source-homekit)"
tags: [ Discovery ]
responses:
default:
description: Default response
/api/nest:
get:
summary: Nest cameras discovery
tags: [ Discovery ]
responses:
default:
description: Default response
/api/onvif:
get:
summary: ONVIF cameras discovery
description: "[Source: ONVIF](https://github.com/AlexxIT/go2rtc#source-onvif)"
tags: [ Discovery ]
responses:
default:
description: Default response
/api/roborock:
get:
summary: Roborock vacuums discovery
description: "[Source: Roborock](https://github.com/AlexxIT/go2rtc#source-roborock)"
tags: [ Discovery ]
responses:
default:
description: Default response
/onvif/:
get:
summary: ONVIF server implementation
description: Simple realisation of the ONVIF protocol. Accepts any suburl requests
tags: [ ONVIF ]
responses:
default:
description: Default response
/stream/:
get:
summary: RTSPtoWebRTC server implementation
description: Simple API for support [RTSPtoWebRTC](https://www.home-assistant.io/integrations/rtsp_to_webrtc/) integration
tags: [ RTSPtoWebRTC ]
responses:
default:
description: Default response
/api/webtorrent?src={src}:
get:
summary: Get WebTorrent share info
tags: [ WebTorrent ]
parameters:
- $ref: "#/components/parameters/stream_src_path"
responses:
200: { $ref: "#/components/responses/webtorrent" }
post:
summary: Add WebTorrent share
tags: [ WebTorrent ]
parameters:
- $ref: "#/components/parameters/stream_src_path"
responses:
200: { $ref: "#/components/responses/webtorrent" }
delete:
summary: Delete WebTorrent share
tags: [ WebTorrent ]
parameters:
- $ref: "#/components/parameters/stream_src_path"
responses:
default:
description: Default response
/api/webtorrent:
get:
summary: Get all WebTorrent shares info
tags: [ WebTorrent ]
responses:
200: { $ref: "#/components/responses/discovery" }
/api/stack:
get:
summary: Show list unknown goroutines
tags: [ Debug ]
responses:
200:
description: ""
content: { text/plain: { example: "" } }
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

+15 -5
View File
@@ -1,8 +1,8 @@
# syntax=docker/dockerfile:labs
# 0. Prepare images
ARG PYTHON_VERSION="3.11"
ARG GO_VERSION="1.25"
ARG PYTHON_VERSION="3.13"
ARG GO_VERSION="1.26"
# 1. Build go2rtc binary
@@ -26,7 +26,15 @@ COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build CGO_ENABLED=0 go build -ldflags "-s -w" -trimpath
# 2. Final image
# 2. Download CDN dependencies for offline web UI
FROM alpine AS download-cdn
RUN apk add --no-cache wget
COPY www/ /web/
COPY docker/download_cdn.sh /tmp/
RUN sh /tmp/download_cdn.sh /web
# 3. Final image
FROM python:${PYTHON_VERSION}-alpine AS base
# Install ffmpeg, tini (for signal handling),
@@ -46,9 +54,11 @@ RUN if [ "${TARGETARCH}" = "amd64" ]; then apk add --no-cache libva-intel-driver
# RUN libva-vdpau-driver mesa-vdpau-gallium (+150MB total)
COPY --from=build /build/go2rtc /usr/local/bin/
COPY --from=download-cdn /web /var/www/go2rtc
COPY --chmod=755 docker/entrypoint.sh /usr/local/bin/
ENTRYPOINT ["/sbin/tini", "--"]
EXPOSE 1984 8554 8555 8555/udp
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/entrypoint.sh"]
VOLUME /config
WORKDIR /config
CMD ["go2rtc", "-config", "/config/go2rtc.yaml"]
+10 -6
View File
@@ -1,11 +1,15 @@
# Docker
Images are built automatically via [GitHub actions](https://github.com/AlexxIT/go2rtc/actions) and published on [Docker Hub](https://hub.docker.com/r/alexxit/go2rtc) and [GitHub](https://github.com/AlexxIT/go2rtc/pkgs/container/go2rtc).
## Versions
- `alexxit/go2rtc:latest` - latest release based on `alpine` (`amd64`, `386`, `arm/v6`, `arm/v7`, `arm64`) with support hardware transcoding for Intel iGPU and Raspberry
- `alexxit/go2rtc:latest-hardware` - latest release based on `debian 13` (`amd64`) with support hardware transcoding for Intel iGPU, AMD GPU and NVidia GPU
- `alexxit/go2rtc:latest-rockchip` - latest release based on `debian 12` (`arm64`) with support hardware transcoding for Rockchip RK35xx
- `alexxit/go2rtc:master` - latest unstable version based on `alpine`
- `alexxit/go2rtc:master-hardware` - latest unstable version based on `debian 13` (`amd64`)
- `alexxit/go2rtc:master-rockchip` - latest unstable version based on `debian 12` (`arm64`)
- `alexxit/go2rtc:latest` - latest release based on `alpine` (`amd64`, `386`, `arm/v6`, `arm/v7`, `arm64`) with support for hardware transcoding for Intel iGPU and Raspberry
- `alexxit/go2rtc:latest-hardware` - latest release based on `debian 13` (`amd64`) with support for hardware transcoding for Intel iGPU, AMD GPU and NVidia GPU
- `alexxit/go2rtc:latest-rockchip` - latest release based on `debian 12` (`arm64`) with support for hardware transcoding for Rockchip RK35xx
- `ghcr.io/AlexxIT/go2rtc:dev` - latest unstable version based on `alpine`
- `ghcr.io/AlexxIT/go2rtc:dev-hardware` - latest unstable version based on `debian 13` (`amd64`)
- `ghcr.io/AlexxIT/go2rtc:dev-rockchip` - latest unstable version based on `debian 12` (`arm64`)
## Docker compose
+407
View File
@@ -0,0 +1,407 @@
package docker_test
import (
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"testing"
)
// cdnURLPattern is the same regex used by download_cdn.sh to extract CDN URLs.
var cdnURLPattern = regexp.MustCompile(`https://cdn\.jsdelivr\.net/npm/[^"' )\x60]*`)
// HTML fixtures that mirror the real www/*.html files.
var htmlFixtures = map[string]string{
"hls.html": `<!doctype html>
<html lang="en">
<body>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script>
<video id="video"></video>
</body>
</html>`,
"config.html": `<!DOCTYPE html>
<html lang="en">
<body>
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs/loader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js"></script>
<script>
const monacoRoot = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min';
window.MonacoEnvironment = {
getWorkerUrl: function () {
return ` + "`" + `data:text/javascript;charset=utf-8,${encodeURIComponent(` + "`" + `
self.MonacoEnvironment = { baseUrl: '${monacoRoot}/' };
importScripts('${monacoRoot}/vs/base/worker/workerMain.js');
` + "`" + `)}` + "`" + `;
}
};
require.config({paths: {vs: ` + "`" + `${monacoRoot}/vs` + "`" + `}});
</script>
</body>
</html>`,
"net.html": `<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdn.jsdelivr.net/npm/vis-network@10.0.2/standalone/umd/vis-network.min.js"></script>
</head>
<body></body>
</html>`,
"links.html": `<!DOCTYPE html>
<html lang="en">
<body>
<script>
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js';
document.head.appendChild(script);
</script>
</body>
</html>`,
}
func parseCDNURL(rawURL string) (pkgName, filePath, localURL string) {
npmPath := strings.TrimPrefix(rawURL, "https://cdn.jsdelivr.net/npm/")
parts := strings.SplitN(npmPath, "/", 2)
pkgVer := parts[0]
if len(parts) > 1 {
filePath = parts[1]
}
// Remove @version suffix to get package name
if idx := strings.LastIndex(pkgVer, "@"); idx > 0 {
pkgName = pkgVer[:idx]
} else {
pkgName = pkgVer
}
if filePath != "" {
localURL = "cdn/" + pkgName + "/" + filePath
} else {
localURL = "cdn/" + pkgName + "/index.js"
}
return
}
// extractURLs finds all CDN URLs in the given HTML content.
func extractURLs(htmlFiles map[string]string) []string {
seen := map[string]bool{}
for _, content := range htmlFiles {
for _, match := range cdnURLPattern.FindAllString(content, -1) {
seen[match] = true
}
}
urls := make([]string, 0, len(seen))
for u := range seen {
urls = append(urls, u)
}
sort.Strings(urls)
return urls
}
// patchHTML replaces all CDN URLs in content with local paths.
func patchHTML(content string, urls []string) string {
for _, u := range urls {
_, _, localURL := parseCDNURL(u)
content = strings.ReplaceAll(content, u, localURL)
}
return content
}
func TestExtractURLs(t *testing.T) {
urls := extractURLs(htmlFixtures)
expected := []string{
"https://cdn.jsdelivr.net/npm/hls.js@1",
"https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js",
"https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min",
"https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs/loader.js",
"https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js",
"https://cdn.jsdelivr.net/npm/vis-network@10.0.2/standalone/umd/vis-network.min.js",
}
if len(urls) != len(expected) {
t.Fatalf("expected %d URLs, got %d: %v", len(expected), len(urls), urls)
}
for i, u := range urls {
if u != expected[i] {
t.Errorf("URL[%d]: expected %q, got %q", i, expected[i], u)
}
}
}
func TestParseCDNURL(t *testing.T) {
tests := []struct {
url string
pkgName string
filePath string
localURL string
}{
{
url: "https://cdn.jsdelivr.net/npm/hls.js@1",
pkgName: "hls.js",
filePath: "",
localURL: "cdn/hls.js/index.js",
},
{
url: "https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js",
pkgName: "js-yaml",
filePath: "dist/js-yaml.min.js",
localURL: "cdn/js-yaml/dist/js-yaml.min.js",
},
{
url: "https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs/loader.js",
pkgName: "monaco-editor",
filePath: "min/vs/loader.js",
localURL: "cdn/monaco-editor/min/vs/loader.js",
},
{
url: "https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min",
pkgName: "monaco-editor",
filePath: "min",
localURL: "cdn/monaco-editor/min",
},
{
url: "https://cdn.jsdelivr.net/npm/vis-network@10.0.2/standalone/umd/vis-network.min.js",
pkgName: "vis-network",
filePath: "standalone/umd/vis-network.min.js",
localURL: "cdn/vis-network/standalone/umd/vis-network.min.js",
},
{
url: "https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js",
pkgName: "qrcodejs",
filePath: "qrcode.min.js",
localURL: "cdn/qrcodejs/qrcode.min.js",
},
}
for _, tt := range tests {
t.Run(tt.pkgName, func(t *testing.T) {
pkgName, filePath, localURL := parseCDNURL(tt.url)
if pkgName != tt.pkgName {
t.Errorf("pkgName: expected %q, got %q", tt.pkgName, pkgName)
}
if filePath != tt.filePath {
t.Errorf("filePath: expected %q, got %q", tt.filePath, filePath)
}
if localURL != tt.localURL {
t.Errorf("localURL: expected %q, got %q", tt.localURL, localURL)
}
})
}
}
func TestPatchHTML(t *testing.T) {
urls := extractURLs(htmlFixtures)
t.Run("hls", func(t *testing.T) {
patched := patchHTML(htmlFixtures["hls.html"], urls)
if !strings.Contains(patched, `src="cdn/hls.js/index.js"`) {
t.Error("hls.js src not patched")
}
if strings.Contains(patched, "cdn.jsdelivr.net") {
t.Error("CDN URL still present")
}
if !strings.Contains(patched, `<video id="video">`) {
t.Error("non-CDN content damaged")
}
})
t.Run("config", func(t *testing.T) {
patched := patchHTML(htmlFixtures["config.html"], urls)
if !strings.Contains(patched, `src="cdn/monaco-editor/min/vs/loader.js"`) {
t.Error("monaco loader.js src not patched")
}
if !strings.Contains(patched, `src="cdn/js-yaml/dist/js-yaml.min.js"`) {
t.Error("js-yaml src not patched")
}
if !strings.Contains(patched, "monacoRoot = 'cdn/monaco-editor/min'") {
t.Error("monacoRoot variable not patched")
}
// Dynamic references via ${monacoRoot} must remain untouched
if !strings.Contains(patched, "${monacoRoot}/") {
t.Error("dynamic monacoRoot references damaged")
}
if strings.Contains(patched, "cdn.jsdelivr.net") {
t.Error("CDN URL still present")
}
if !strings.Contains(patched, "require.config") {
t.Error("non-CDN content damaged")
}
})
t.Run("net", func(t *testing.T) {
patched := patchHTML(htmlFixtures["net.html"], urls)
if !strings.Contains(patched, `src="cdn/vis-network/standalone/umd/vis-network.min.js"`) {
t.Error("vis-network src not patched")
}
if strings.Contains(patched, "cdn.jsdelivr.net") {
t.Error("CDN URL still present")
}
})
t.Run("links", func(t *testing.T) {
patched := patchHTML(htmlFixtures["links.html"], urls)
if !strings.Contains(patched, "src = 'cdn/qrcodejs/qrcode.min.js'") {
t.Error("qrcodejs src not patched")
}
if strings.Contains(patched, "cdn.jsdelivr.net") {
t.Error("CDN URL still present")
}
if !strings.Contains(patched, "document.createElement") {
t.Error("non-CDN content damaged")
}
})
}
func TestExtractURLsFromRealFiles(t *testing.T) {
// Verify the regex works against the actual www/*.html files
wwwDir := filepath.Join("..", "www")
entries, err := filepath.Glob(filepath.Join(wwwDir, "*.html"))
if err != nil || len(entries) == 0 {
t.Skip("www/*.html not found, skipping real file test")
}
realFiles := map[string]string{}
for _, path := range entries {
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("reading %s: %v", path, err)
}
realFiles[filepath.Base(path)] = string(data)
}
urls := extractURLs(realFiles)
if len(urls) < 5 {
t.Errorf("expected at least 5 CDN URLs in real files, got %d: %v", len(urls), urls)
}
// Every URL must be parseable
for _, u := range urls {
pkgName, _, localURL := parseCDNURL(u)
if pkgName == "" {
t.Errorf("failed to parse package name from %q", u)
}
if localURL == "" {
t.Errorf("failed to generate local URL for %q", u)
}
}
}
func TestMonacoVersionExtraction(t *testing.T) {
urls := extractURLs(htmlFixtures)
var monacoVer string
for _, u := range urls {
pkgName, _, _ := parseCDNURL(u)
if pkgName == "monaco-editor" {
npmPath := strings.TrimPrefix(u, "https://cdn.jsdelivr.net/npm/")
pkgVer := strings.SplitN(npmPath, "/", 2)[0]
if idx := strings.LastIndex(pkgVer, "@"); idx > 0 {
monacoVer = pkgVer[idx+1:]
}
break
}
}
if monacoVer != "0.55.1" {
t.Errorf("expected monaco version 0.55.1, got %q", monacoVer)
}
}
func TestEntrypoint(t *testing.T) {
// Find the entrypoint.sh relative to the test file
entrypoint := filepath.Join("entrypoint.sh")
if _, err := os.Stat(entrypoint); err != nil {
t.Skipf("entrypoint.sh not found: %v", err)
}
tmpDir := t.TempDir()
// Create a mock go2rtc that prints its arguments
mockBin := filepath.Join(tmpDir, "go2rtc")
os.WriteFile(mockBin, []byte("#!/bin/sh\necho \"$@\"\n"), 0755)
// Read the entrypoint script and adapt for testing:
// replace "exec " with "" so the mock go2rtc output is captured,
// replace hardcoded /var/www/go2rtc with our temp path.
data, err := os.ReadFile(entrypoint)
if err != nil {
t.Fatal(err)
}
webDir := filepath.Join(tmpDir, "var", "www", "go2rtc")
script := strings.ReplaceAll(string(data), "exec ", "")
script = strings.ReplaceAll(script, "/var/www/go2rtc", webDir)
script = strings.ReplaceAll(script, "/config/go2rtc.yaml", "/tmp/test.yaml")
testScript := filepath.Join(tmpDir, "test_entrypoint.sh")
os.WriteFile(testScript, []byte(script), 0755)
run := func(extraArgs ...string) string {
args := append([]string{testScript}, extraArgs...)
cmd := exec.Command("sh", args...)
cmd.Env = append(os.Environ(), "PATH="+tmpDir+":"+os.Getenv("PATH"))
out, err := cmd.Output()
if err != nil {
t.Fatalf("entrypoint failed: %v", err)
}
return strings.TrimSpace(string(out))
}
t.Run("with_web_dir", func(t *testing.T) {
os.MkdirAll(webDir, 0755)
defer os.RemoveAll(filepath.Join(tmpDir, "var"))
result := run("--extra-flag")
if !strings.Contains(result, "static_dir") {
t.Error("static_dir not added when web dir exists")
}
if !strings.Contains(result, "-config /tmp/test.yaml") {
t.Error("user config not present")
}
if !strings.Contains(result, "--extra-flag") {
t.Error("extra args not passed through")
}
})
t.Run("without_web_dir", func(t *testing.T) {
os.RemoveAll(filepath.Join(tmpDir, "var"))
result := run("--extra-flag")
if strings.Contains(result, "static_dir") {
t.Error("static_dir added when web dir absent")
}
if !strings.Contains(result, "-config /tmp/test.yaml") {
t.Error("user config not present")
}
if !strings.Contains(result, "--extra-flag") {
t.Error("extra args not passed through")
}
})
t.Run("config_order", func(t *testing.T) {
os.MkdirAll(webDir, 0755)
defer os.RemoveAll(filepath.Join(tmpDir, "var"))
result := run()
// static_dir config must come BEFORE user config
// so user config can override it
staticIdx := strings.Index(result, "static_dir")
yamlIdx := strings.Index(result, "/tmp/test.yaml")
if staticIdx < 0 || yamlIdx < 0 {
t.Fatalf("expected both configs in output: %q", result)
}
if staticIdx > yamlIdx {
t.Errorf("static_dir config should come before user config for correct override order, got: %q", result)
}
})
}
+105
View File
@@ -0,0 +1,105 @@
#!/bin/sh
# Downloads CDN dependencies from jsdelivr for offline web UI.
# Automatically parses CDN URLs from HTML files, so updating
# a library version in HTML is all that's needed.
#
# Usage: download_cdn.sh <web_dir>
set -e
WEB_DIR="${1:?Usage: download_cdn.sh <web_dir>}"
CDN_DIR="$WEB_DIR/cdn"
mkdir -p "$CDN_DIR"
# Step 1: Extract all jsdelivr CDN URLs from HTML files
URLS=$(grep -roh 'https://cdn\.jsdelivr\.net/npm/[^"'"'"' )`]*' "$WEB_DIR"/*.html | sort -u)
echo "=== Found CDN URLs ==="
echo "$URLS"
echo ""
# Step 2: Process each URL
MONACO_VER=""
for url in $URLS; do
# Remove CDN prefix to get npm path
npm_path="${url#https://cdn.jsdelivr.net/npm/}"
# Extract package@version and file path
pkg_ver=$(echo "$npm_path" | cut -d/ -f1)
remaining=$(echo "$npm_path" | cut -d/ -f2-)
if [ "$remaining" = "$npm_path" ]; then
file_path=""
else
file_path="$remaining"
fi
pkg_name=$(echo "$pkg_ver" | sed 's/@[^@]*$//')
# Monaco editor: remember version, download as tarball later
case "$pkg_name" in
monaco-editor)
MONACO_VER=$(echo "$pkg_ver" | sed 's/.*@//')
echo "Monaco editor v$MONACO_VER (will download tarball)"
continue
;;
esac
# Determine local file path
if [ -n "$file_path" ]; then
local_file="$CDN_DIR/$pkg_name/$file_path"
else
local_file="$CDN_DIR/$pkg_name/index.js"
fi
mkdir -p "$(dirname "$local_file")"
echo "Downloading $pkg_ver -> $local_file"
wget -q -O "$local_file" "$url"
done
# Step 3: Download monaco-editor tarball and extract min/ directory
# The AMD loader dynamically loads modules, so we need the entire min/vs/ tree
if [ -n "$MONACO_VER" ]; then
echo ""
echo "=== Downloading monaco-editor@$MONACO_VER tarball ==="
TARBALL_URL=$(wget -q -O - "https://registry.npmjs.org/monaco-editor/$MONACO_VER" | \
grep -o '"tarball":"[^"]*"' | head -1 | cut -d'"' -f4)
mkdir -p /tmp/monaco "$CDN_DIR/monaco-editor"
wget -q -O /tmp/monaco.tgz "$TARBALL_URL"
tar xzf /tmp/monaco.tgz -C /tmp/monaco
cp -r /tmp/monaco/package/min "$CDN_DIR/monaco-editor/"
rm -rf /tmp/monaco /tmp/monaco.tgz
echo " Extracted min/ directory ($(du -sh "$CDN_DIR/monaco-editor/min" | cut -f1))"
fi
# Step 4: Patch HTML files to use local paths instead of CDN URLs
echo ""
echo "=== Patching HTML files ==="
for url in $URLS; do
npm_path="${url#https://cdn.jsdelivr.net/npm/}"
pkg_ver=$(echo "$npm_path" | cut -d/ -f1)
remaining=$(echo "$npm_path" | cut -d/ -f2-)
if [ "$remaining" = "$npm_path" ]; then
file_path=""
else
file_path="$remaining"
fi
pkg_name=$(echo "$pkg_ver" | sed 's/@[^@]*$//')
if [ -n "$file_path" ]; then
local_url="cdn/$pkg_name/$file_path"
else
local_url="cdn/$pkg_name/index.js"
fi
echo " $url -> $local_url"
sed -i "s|$url|$local_url|g" "$WEB_DIR"/*.html
done
echo ""
echo "=== Done ==="
du -sh "$CDN_DIR"
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
# Entrypoint wrapper for go2rtc Docker container.
# If /var/www/go2rtc exists (CDN files bundled), automatically
# configures static_dir to serve web UI without internet access.
if [ -d /var/www/go2rtc ]; then
exec go2rtc \
-config '{"api":{"static_dir":"/var/www/go2rtc"}}' \
-config /config/go2rtc.yaml \
"$@"
else
exec go2rtc -config /config/go2rtc.yaml "$@"
fi
+14 -4
View File
@@ -4,7 +4,7 @@
# only debian 13 (trixie) has latest ffmpeg
# https://packages.debian.org/trixie/ffmpeg
ARG DEBIAN_VERSION="trixie-slim"
ARG GO_VERSION="1.25-bookworm"
ARG GO_VERSION="1.26-bookworm"
# 1. Build go2rtc binary
@@ -26,7 +26,15 @@ COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build CGO_ENABLED=0 go build -ldflags "-s -w" -trimpath
# 2. Final image
# 2. Download CDN dependencies for offline web UI
FROM alpine AS download-cdn
RUN apk add --no-cache wget
COPY www/ /web/
COPY docker/download_cdn.sh /tmp/
RUN sh /tmp/download_cdn.sh /web
# 3. Final image
FROM debian:${DEBIAN_VERSION}
# Prepare apt for buildkit cache
@@ -48,12 +56,14 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked --mount=type=cache,t
apt-get clean && rm -rf /var/lib/apt/lists/*
COPY --from=build /build/go2rtc /usr/local/bin/
COPY --from=download-cdn /web /var/www/go2rtc
COPY --chmod=755 docker/entrypoint.sh /usr/local/bin/
ENTRYPOINT ["/usr/bin/tini", "--"]
EXPOSE 1984 8554 8555 8555/udp
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"]
VOLUME /config
WORKDIR /config
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_VISIBLE_DEVICES all
ENV NVIDIA_DRIVER_CAPABILITIES compute,video,utility
CMD ["go2rtc", "-config", "/config/go2rtc.yaml"]
+14 -5
View File
@@ -2,7 +2,7 @@
# 0. Prepare images
ARG PYTHON_VERSION="3.13-slim-bookworm"
ARG GO_VERSION="1.25-bookworm"
ARG GO_VERSION="1.26-bookworm"
# 1. Build go2rtc binary
@@ -24,7 +24,15 @@ COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build CGO_ENABLED=0 go build -ldflags "-s -w" -trimpath
# 2. Final image
# 2. Download CDN dependencies for offline web UI
FROM alpine AS download-cdn
RUN apk add --no-cache wget
COPY www/ /web/
COPY docker/download_cdn.sh /tmp/
RUN sh /tmp/download_cdn.sh /web
# 3. Final image
FROM python:${PYTHON_VERSION}
# Prepare apt for buildkit cache
@@ -42,9 +50,10 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked --mount=type=cache,t
COPY --from=build /build/go2rtc /usr/local/bin/
ADD --chmod=755 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.1-8-no_extra_dump/ffmpeg /usr/local/bin
COPY --from=download-cdn /web /var/www/go2rtc
COPY --chmod=755 docker/entrypoint.sh /usr/local/bin/
ENTRYPOINT ["/usr/bin/tini", "--"]
EXPOSE 1984 8554 8555 8555/udp
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"]
VOLUME /config
WORKDIR /config
CMD ["go2rtc", "-config", "/config/go2rtc.yaml"]
+9
View File
@@ -0,0 +1,9 @@
module pinggy
go 1.26
require (
github.com/Pinggy-io/pinggy-go/pinggy v0.6.9 // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/sys v0.7.0 // indirect
)
+39
View File
@@ -0,0 +1,39 @@
github.com/Pinggy-io/pinggy-go/pinggy v0.6.9 h1:lzZ00JK6BUGQXnpkJZ+cVj8kIkXsmiVBUci9uEkSwEY=
github.com/Pinggy-io/pinggy-go/pinggy v0.6.9/go.mod h1:V1Sxb+4zyr36o9atZiqtT4XhsKtW1RSb2GvsbTbTJYw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+41
View File
@@ -0,0 +1,41 @@
package main
import (
"log"
"os"
"github.com/Pinggy-io/pinggy-go/pinggy"
)
func main() {
tunType := os.Args[1]
address := os.Args[2]
log.SetFlags(log.Llongfile | log.LstdFlags)
config := pinggy.Config{
Type: pinggy.TunnelType(tunType),
TcpForwardingAddr: address,
//SshOverSsl: true,
//Stdout: os.Stderr,
//Stderr: os.Stderr,
}
if tunType == "http" {
hman := pinggy.CreateHeaderManipulationAndAuthConfig()
//hman.SetReverseProxy(address)
//hman.SetPassPreflight(true)
//hman.SetNoReverseProxy()
config.HeaderManipulationAndAuth = hman
}
pl, err := pinggy.ConnectWithConfig(config)
if err != nil {
log.Panicln(err)
}
log.Println("Addrs: ", pl.RemoteUrls())
//err = pl.InitiateWebDebug("localhost:3424")
//log.Println(err)
pl.StartForwarding()
}
+1 -1
View File
@@ -1,4 +1,4 @@
## Example
## ONVIF Client
```shell
go run examples/onvif_client/main.go http://admin:password@192.168.10.90 GetAudioEncoderConfigurations
+5
View File
@@ -0,0 +1,5 @@
# tutk_decoder
1. Wireshark > Select any packet > Follow > UDP Stream
2. Wireshark > File > Export Packet Dissections > As JSON > Displayed, Values
3. `tutk_decoder wireshark.json decoded.txt`
+82
View File
@@ -0,0 +1,82 @@
package main
import (
"encoding/hex"
"encoding/json"
"fmt"
"log"
"os"
"strings"
"github.com/AlexxIT/go2rtc/pkg/tutk"
)
func main() {
if len(os.Args) != 3 {
fmt.Println("Usage: tutk_decoder wireshark.json decoded.txt")
return
}
src, err := os.Open(os.Args[1])
if err != nil {
log.Fatal(err)
}
defer src.Close()
dst, err := os.Create(os.Args[2])
if err != nil {
log.Fatal(err)
}
defer dst.Close()
var items []item
if err = json.NewDecoder(src).Decode(&items); err != nil {
log.Fatal(err)
}
var b []byte
for _, v := range items {
if v.Source.Layers.Data.DataData == "" {
continue
}
s := strings.ReplaceAll(v.Source.Layers.Data.DataData, ":", "")
b, err = hex.DecodeString(s)
if err != nil {
log.Fatal(err)
}
tutk.ReverseTransCodePartial(b, b)
ts := v.Source.Layers.Frame.FrameTimeRelative
_, _ = fmt.Fprintf(dst, "%8s: %s -> %s [%4d] %x\n",
ts[:len(ts)-6],
v.Source.Layers.Ip.IpSrc, v.Source.Layers.Ip.IpDst,
len(b), b)
}
}
type item struct {
Source struct {
Layers struct {
Frame struct {
FrameTimeRelative string `json:"frame.time_relative"`
FrameNumber string `json:"frame.number"`
} `json:"frame"`
Ip struct {
IpSrc string `json:"ip.src"`
IpDst string `json:"ip.dst"`
} `json:"ip"`
Udp struct {
UdpSrcport string `json:"udp.srcport"`
UdpDstport string `json:"udp.dstport"`
} `json:"udp"`
Data struct {
DataData string `json:"data.data"`
DataLen string `json:"data.len"`
} `json:"data"`
} `json:"layers"`
} `json:"_source"`
}
+27 -26
View File
@@ -1,49 +1,50 @@
module github.com/AlexxIT/go2rtc
go 1.23.0
go 1.26.1
require (
github.com/asticode/go-astits v1.13.0
github.com/expr-lang/expr v1.17.5
github.com/eclipse/paho.mqtt.golang v1.5.1
github.com/expr-lang/expr v1.17.7
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/mattn/go-isatty v0.0.20
github.com/miekg/dns v1.1.66
github.com/pion/ice/v4 v4.0.10
github.com/pion/interceptor v0.1.40
github.com/pion/rtcp v1.2.15
github.com/pion/rtp v1.8.20
github.com/pion/sdp/v3 v3.0.14
github.com/pion/srtp/v3 v3.0.6
github.com/pion/stun/v3 v3.0.0
github.com/pion/webrtc/v4 v4.1.3
github.com/miekg/dns v1.1.70
github.com/pion/dtls/v3 v3.0.10
github.com/pion/ice/v4 v4.2.0
github.com/pion/interceptor v0.1.43
github.com/pion/rtcp v1.2.16
github.com/pion/rtp v1.10.0
github.com/pion/sdp/v3 v3.0.17
github.com/pion/srtp/v3 v3.0.10
github.com/pion/stun/v3 v3.1.1
github.com/pion/webrtc/v4 v4.2.3
github.com/rs/zerolog v1.34.0
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f
github.com/stretchr/testify v1.10.0
github.com/skrashevich/go-webp v0.1.0
github.com/stretchr/testify v1.11.1
github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9
golang.org/x/crypto v0.39.0
golang.org/x/crypto v0.47.0
golang.org/x/net v0.49.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/asticode/go-astikit v0.56.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/pion/datachannel v1.5.10 // indirect
github.com/pion/dtls/v3 v3.0.6 // indirect
github.com/pion/datachannel v1.6.0 // indirect
github.com/pion/logging v0.2.4 // indirect
github.com/pion/mdns/v2 v2.0.7 // indirect
github.com/pion/mdns/v2 v2.1.0 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/sctp v1.8.39 // indirect
github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pion/turn/v4 v4.0.2 // indirect
github.com/pion/sctp v1.9.2 // indirect
github.com/pion/transport/v4 v4.0.1 // indirect
github.com/pion/turn/v4 v4.1.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/tools v0.34.0 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.41.0 // indirect
)
+61 -92
View File
@@ -1,20 +1,14 @@
github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
github.com/asticode/go-astikit v0.54.0 h1:uq9eurgisdkYwJU9vSWIQaPH4MH0cac82sQH00kmSNQ=
github.com/asticode/go-astikit v0.54.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE=
github.com/asticode/go-astikit v0.56.0 h1:DmD2p7YnvxiPdF0h+dRmos3bsejNEXbycENsY5JfBqw=
github.com/asticode/go-astikit v0.56.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE=
github.com/asticode/go-astits v1.13.0 h1:XOgkaadfZODnyZRR5Y0/DWkA9vrkLLPLeeOvDwfKZ1c=
github.com/asticode/go-astits v1.13.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/expr-lang/expr v1.17.2 h1:o0A99O/Px+/DTjEnQiodAgOIK9PPxL8DtXhBRKC+Iso=
github.com/expr-lang/expr v1.17.2/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/expr-lang/expr v1.17.5 h1:i1WrMvcdLF249nSNlpQZN1S6NXuW9WaOfF5tPi3aw3k=
github.com/expr-lang/expr v1.17.5/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8=
github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
@@ -30,63 +24,44 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E=
github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU=
github.com/pion/ice/v4 v4.0.9 h1:VKgU4MwA2LUDVLq+WBkpEHTcAb8c5iCvFMECeuPOZNk=
github.com/pion/ice/v4 v4.0.9/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI=
github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y=
github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4=
github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic=
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/pion/datachannel v1.6.0 h1:XecBlj+cvsxhAMZWFfFcPyUaDZtd7IJvrXqlXD/53i0=
github.com/pion/datachannel v1.6.0/go.mod h1:ur+wzYF8mWdC+Mkis5Thosk+u/VOL287apDNEbFpsIk=
github.com/pion/dtls/v3 v3.0.10 h1:k9ekkq1kaZoxnNEbyLKI8DI37j/Nbk1HWmMuywpQJgg=
github.com/pion/dtls/v3 v3.0.10/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8=
github.com/pion/ice/v4 v4.2.0 h1:jJC8S+CvXCCvIQUgx+oNZnoUpt6zwc34FhjWwCU4nlw=
github.com/pion/ice/v4 v4.2.0/go.mod h1:EgjBGxDgmd8xB0OkYEVFlzQuEI7kWSCFu+mULqaisy4=
github.com/pion/interceptor v0.1.43 h1:6hmRfnmjogSs300xfkR0JxYFZ9k5blTEvCD7wxEDuNQ=
github.com/pion/interceptor v0.1.43/go.mod h1:BSiC1qKIJt1XVr3l3xQ2GEmCFStk9tx8fwtCZxxgR7M=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY=
github.com/pion/mdns/v2 v2.1.0/go.mod h1:pcez23GdynwcfRU1977qKU0mDxSeucttSHbCSfFOd9A=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
github.com/pion/rtp v1.8.13 h1:8uSUPpjSL4OlwZI8Ygqu7+h2p9NPFB+yAZ461Xn5sNg=
github.com/pion/rtp v1.8.13/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4=
github.com/pion/rtp v1.8.20 h1:8zcyqohadZE8FCBeGdyEvHiclPIezcwRQH9zfapFyYI=
github.com/pion/rtp v1.8.20/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
github.com/pion/sctp v1.8.37 h1:ZDmGPtRPX9mKCiVXtMbTWybFw3z/hVKAZgU81wcOrqs=
github.com/pion/sctp v1.8.37/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
github.com/pion/sdp/v3 v3.0.11 h1:VhgVSopdsBKwhCFoyyPmT1fKMeV9nLMrEKxNOdy3IVI=
github.com/pion/sdp/v3 v3.0.11/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI=
github.com/pion/sdp/v3 v3.0.14/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M=
github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ=
github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4=
github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY=
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM=
github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA=
github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps=
github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs=
github.com/pion/webrtc/v4 v4.0.14 h1:nyds/sFRR+HvmWoBa6wrL46sSfpArE0qR883MBW96lg=
github.com/pion/webrtc/v4 v4.0.14/go.mod h1:R3+qTnQTS03UzwDarYecgioNf7DYgTsldxnCXB821Kk=
github.com/pion/webrtc/v4 v4.1.3 h1:YZ67Boj9X/hk190jJZ8+HFGQ6DqSZ/fYP3sLAZv7c3c=
github.com/pion/webrtc/v4 v4.1.3/go.mod h1:rsq+zQ82ryfR9vbb0L1umPJ6Ogq7zm8mcn9fcGnxomM=
github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
github.com/pion/rtp v1.10.0 h1:XN/xca4ho6ZEcijpdF2VGFbwuHUfiIMf3ew8eAAE43w=
github.com/pion/rtp v1.10.0/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
github.com/pion/sctp v1.9.2 h1:HxsOzEV9pWoeggv7T5kewVkstFNcGvhMPx0GvUOUQXo=
github.com/pion/sctp v1.9.2/go.mod h1:OTOlsQ5EDQ6mQ0z4MUGXt2CgQmKyafBEXhUVqLRB6G8=
github.com/pion/sdp/v3 v3.0.17 h1:9SfLAW/fF1XC8yRqQ3iWGzxkySxup4k4V7yN8Fs8nuo=
github.com/pion/sdp/v3 v3.0.17/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo=
github.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ=
github.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M=
github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw=
github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM=
github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM=
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
github.com/pion/turn/v4 v4.1.4 h1:EU11yMXKIsK43FhcUnjLlrhE4nboHZq+TXBIi3QpcxQ=
github.com/pion/turn/v4 v4.1.4/go.mod h1:ES1DXVFKnOhuDkqn9hn5VJlSWmZPaRJLyBXoOeO/BmQ=
github.com/pion/webrtc/v4 v4.2.3 h1:RtdWDnkenNQGxUrZqWa5gSkTm5ncsLg5d+zu0M4cXt4=
github.com/pion/webrtc/v4 v4.2.3/go.mod h1:7vsyFzRzaKP5IELUnj8zLcglPyIT6wWwqTppBZ1k6Kc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
@@ -98,43 +73,37 @@ github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 h1:NVK+OqnavpyFmUiKfU
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f h1:1R9KdKjCNSd7F8iGTxIpoID9prlYH8nuNYKt0XvweHA=
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f/go.mod h1:vQhwQ4meQEDfahT5kd61wLAF5AAeh5ZPLVI4JJ/tYo8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/skrashevich/go-webp v0.1.0 h1:C+dtldBorS5ISATYR5mvG9HFj8GRLDGRywT0xs/ZLUQ=
github.com/skrashevich/go-webp v0.1.0/go.mod h1:9QtuNP/H9q/qzqgaZeYalNIk7n5lfyqVs1WTaPtC/Ao=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 h1:aeN+ghOV0b2VCmKKO3gqnDQ8mLbpABZgRR2FVYx4ouI=
github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9/go.mod h1:roo6cZ/uqpwKMuvPG0YmzI5+AmUiMWfjCBZpGXqbTxE=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc=
golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+113
View File
@@ -0,0 +1,113 @@
# Modules
go2rtc tries to name formats, protocols and codecs the same way they are named in FFmpeg.
Some formats and protocols go2rtc supports exclusively. They have no equivalent in FFmpeg.
- The [`echo`], [`expr`], [`hass`] and [`onvif`] modules receive a link to a stream. They don't know the protocol in advance.
- The [`exec`] and [`ffmpeg`] modules support many formats. They are identical to the [`http`] module.
- The [`api`], [`app`], [`debug`], [`ngrok`], [`pinggy`], [`srtp`], [`streams`] are supporting modules.
**Modules** implement communication APIs: authorization, encryption, command set, structure of media packets.
**Formats** describe the structure of the data being transmitted.
**Protocols** implement transport for data transmission.
| module | formats | protocols | input | output | ingest | two-way |
|----------------|-----------------|------------------|-------|--------|--------|---------|
| [`alsa`] | `pcm` | `ioctl` | yes | | | |
| [`bubble`] | - | `http` | yes | | | |
| [`doorbird`] | `mulaw` | `http` | yes | | | yes |
| [`dvrip`] | - | `tcp` | yes | | | yes |
| [`echo`] | * | * | yes | | | |
| [`eseecloud`] | `rtp` | `http` | yes | | | |
| [`exec`] | * | `pipe`, `rtsp` | yes | | | yes |
| [`expr`] | * | * | yes | | | |
| [`ffmpeg`] | * | `pipe`, `rtsp` | yes | | | |
| [`flussonic`] | `mp4` | `ws` | yes | | | |
| [`gopro`] | `mpegts` | `udp` | yes | | | |
| [`hass`] | * | * | yes | | | |
| [`hls`] | `mpegts`, `mp4` | `http` | | yes | | |
| [`homekit`] | `srtp` | `hap` | yes | yes | | no |
| [`http`] | `adts` | `http`, `tcp` | yes | | | |
| [`http`] | `flv` | `http`, `tcp` | yes | | | |
| [`http`] | `h264` | `http`, `tcp` | yes | | | |
| [`http`] | `hevc` | `http`, `tcp` | yes | | | |
| [`http`] | `hls` | `http`, `tcp` | yes | | | |
| [`http`] | `mjpeg` | `http`, `tcp` | yes | | | |
| [`http`] | `mpjpeg` | `http` | yes | | | |
| [`http`] | `mpegts` | `http`, `tcp` | yes | | | |
| [`http`] | `wav` | `http`, `tcp` | yes | | | |
| [`http`] | `yuv4mpegpipe` | `http`, `tcp` | yes | | | |
| [`isapi`] | `alaw`, `mulaw` | `http` | | | | yes |
| [`ivideon`] | `mp4` | `ws` | yes | | | |
| [`kasa`] | `h264`, `mulaw` | `http` | yes | | | |
| [`mjpeg`] | `ascii` | `http` | | yes | | |
| [`mjpeg`] | `jpeg` | `http` | | yes | | |
| [`mjpeg`] | `mpjpeg` | `http` | | yes | yes | |
| [`mjpeg`] | `yuv4mpegpipe` | `http` | | yes | | |
| [`mp4`] | `mp4` | `http`, `ws` | | yes | | |
| [`mpeg`] | `adts` | `http` | | yes | | |
| [`mpeg`] | `mpegts` | `http` | | yes | yes | |
| [`multitrans`] | `rtp` | `tcp` | | | | yes |
| [`nest`] | `srtp` | `rtsp`, `webrtc` | yes | | | no |
| [`onvif`] | `rtp` | * | yes | yes | | |
| [`ring`] | `srtp` | `webrtc` | yes | | | yes |
| [`roborock`] | `srtp` | `webrtc` | yes | | | yes |
| [`rtmp`] | `flv` | `rtmp` | yes | yes | yes | |
| [`rtmp`] | `flv` | `http` | | yes | yes | |
| [`rtsp`] | `rtsp` | `rtsp` | yes | yes | yes | yes |
| [`tapo`] | `mpegts` | `http` | yes | | | yes |
| [`tuya`] | `srtp` | `webrtc` | yes | | | yes |
| [`v4l2`] | `rawvideo` | `ioctl` | yes | | | |
| [`webrtc`] | `srtp` | `webrtc` | yes | yes | yes | yes |
| [`webtorrent`] | `srtp` | `webrtc` | yes | yes | | |
| [`wyoming`] | `pcm` | `tcp` | | yes | | |
| [`wyze`] | - | `tutk` | yes | | | yes |
| [`xiaomi`] | - | `cs2`, `tutk` | yes | | | yes |
| [`yandex`] | `srtp` | `webrtc` | yes | | | |
[`alsa`]: alsa/README.md
[`api`]: api/README.md
[`app`]: app/README.md
[`bubble`]: bubble/README.md
[`debug`]: debug/README.md
[`doorbird`]: doorbird/README.md
[`dvrip`]: dvrip/README.md
[`echo`]: echo/README.md
[`eseecloud`]: eseecloud/README.md
[`exec`]: exec/README.md
[`expr`]: expr/README.md
[`ffmpeg`]: ffmpeg/README.md
[`flussonic`]: flussonic/README.md
[`gopro`]: gopro/README.md
[`hass`]: hass/README.md
[`hls`]: hls/README.md
[`homekit`]: homekit/README.md
[`http`]: http/README.md
[`isapi`]: isapi/README.md
[`ivideon`]: ivideon/README.md
[`kasa`]: kasa/README.md
[`mjpeg`]: mjpeg/README.md
[`mp4`]: mp4/README.md
[`mpeg`]: mpeg/README.md
[`multitrans`]: multitrans/README.md
[`nest`]: nest/README.md
[`ngrok`]: ngrok/README.md
[`onvif`]: onvif/README.md
[`pinggy`]: pinggy/README.md
[`ring`]: ring/README.md
[`roborock`]: roborock/README.md
[`rtmp`]: rtmp/README.md
[`rtsp`]: rtsp/README.md
[`srtp`]: srtp/README.md
[`streams`]: streams/README.md
[`tapo`]: tapo/README.md
[`tuya`]: tuya/README.md
[`v4l2`]: v4l2/README.md
[`webrtc`]: webrtc/README.md
[`webtorrent`]: webtorrent/README.md
[`wyoming`]: wyze/README.md
[`wyze`]: wyze/README.md
[`xiaomi`]: xiaomi/README.md
[`yandex`]: yandex/README.md
+12
View File
@@ -0,0 +1,12 @@
# ALSA
[`new in v1.9.10`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.9.10)
> [!WARNING]
> This source is under development and does not always work well.
[Advanced Linux Sound Architecture](https://en.wikipedia.org/wiki/Advanced_Linux_Sound_Architecture) - a framework for receiving audio from devices on Linux OS.
Easy to add via **WebUI > add > ALSA**.
Alternatively, you can use FFmpeg source.
+44 -3
View File
@@ -1,4 +1,45 @@
## Exit codes
# HTTP API
- https://tldp.org/LDP/abs/html/exitcodes.html
- https://komodor.com/learn/exit-codes-in-containers-and-kubernetes-the-complete-guide/
The HTTP API is the main part for interacting with the application. Default address: `http://localhost:1984/`.
The HTTP API is described in [OpenAPI](../../website/api/openapi.yaml) format. It can be explored in [interactive viewer](https://go2rtc.org/api/). WebSocket API described [here](ws/README.md).
The project's static HTML and JS files are located in the [www](../../www/README.md) folder. An external developer can use them as a basis for integrating go2rtc into their project or for developing a custom web interface for go2rtc.
The contents of `www` folder are built into go2rtc when building, but you can use configuration to specify an external folder as the source of static files.
## Configuration
**Important!** go2rtc passes requests from localhost and Unix sockets without HTTP authorization, even if you have it configured. It is your responsibility to set up secure external access to the API. If not properly configured, an attacker can gain access to your cameras and even your server.
- you can disable HTTP API with `listen: ""` and use, for example, only RTSP client/server protocol
- you can enable HTTP API only on localhost with `listen: "127.0.0.1:1984"` setting
- you can change the API `base_path` and host go2rtc on your main app webserver suburl
- all files from `static_dir` hosted on root path: `/`
- you can use raw TLS cert/key content or path to files
```yaml
api:
listen: ":1984" # default ":1984", HTTP API port ("" - disabled)
username: "admin" # default "", Basic auth for WebUI
password: "pass" # default "", Basic auth for WebUI
local_auth: true # default false, Enable auth check for localhost requests
base_path: "/rtc" # default "", API prefix for serving on suburl (/api => /rtc/api)
static_dir: "www" # default "", folder for static files (custom web interface)
origin: "*" # default "", allow CORS requests (only * supported)
tls_listen: ":443" # default "", enable HTTPS server
tls_cert: | # default "", PEM-encoded fullchain certificate for HTTPS
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
tls_key: | # default "", PEM-encoded private key for HTTPS
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
unix_listen: "/tmp/go2rtc.sock" # default "", unix socket listener for API
```
**PS:**
- MJPEG over WebSocket plays better than native MJPEG because Chrome [bug](https://bugs.chromium.org/p/chromium/issues/detail?id=527446)
- MP4 over WebSocket was created only for Apple iOS because it doesn't support file streaming
+55 -3
View File
@@ -7,6 +7,7 @@ import (
"net"
"net/http"
"os"
"slices"
"strconv"
"strings"
"sync"
@@ -23,6 +24,7 @@ func Init() {
Listen string `yaml:"listen"`
Username string `yaml:"username"`
Password string `yaml:"password"`
LocalAuth bool `yaml:"local_auth"`
BasePath string `yaml:"base_path"`
StaticDir string `yaml:"static_dir"`
Origin string `yaml:"origin"`
@@ -30,6 +32,9 @@ func Init() {
TLSCert string `yaml:"tls_cert"`
TLSKey string `yaml:"tls_key"`
UnixListen string `yaml:"unix_listen"`
ReadOnly bool `yaml:"read_only"`
AllowPaths []string `yaml:"allow_paths"`
} `yaml:"api"`
}
@@ -43,8 +48,12 @@ func Init() {
return
}
allowPaths = cfg.Mod.AllowPaths
basePath = cfg.Mod.BasePath
log = app.GetLogger("api")
ReadOnly = cfg.Mod.ReadOnly
app.ConfigReadOnly = ReadOnly
app.Info["read_only"] = ReadOnly
initStatic(cfg.Mod.StaticDir)
@@ -61,7 +70,7 @@ func Init() {
}
if cfg.Mod.Username != "" {
Handler = middlewareAuth(cfg.Mod.Username, cfg.Mod.Password, Handler) // 2nd
Handler = middlewareAuth(cfg.Mod.Username, cfg.Mod.Password, cfg.Mod.LocalAuth, Handler) // 2nd
}
if log.Trace().Enabled() {
@@ -144,6 +153,15 @@ const (
)
var Handler http.Handler
var ReadOnly bool
func IsReadOnly() bool {
return ReadOnly
}
func ReadOnlyError(w http.ResponseWriter) {
http.Error(w, "read-only", http.StatusForbidden)
}
// HandleFunc handle pattern with relative path:
// - "api/streams" => "{basepath}/api/streams"
@@ -152,6 +170,10 @@ func HandleFunc(pattern string, handler http.HandlerFunc) {
if len(pattern) == 0 || pattern[0] != '/' {
pattern = basePath + "/" + pattern
}
if allowPaths != nil && !slices.Contains(allowPaths, pattern) {
log.Trace().Str("path", pattern).Msg("[api] ignore path not in allow_paths")
return
}
log.Trace().Str("path", pattern).Msg("[api] register path")
http.HandleFunc(pattern, handler)
}
@@ -185,6 +207,7 @@ func Response(w http.ResponseWriter, body any, contentType string) {
const StreamNotFound = "stream not found"
var allowPaths []string
var basePath string
var log zerolog.Logger
@@ -195,9 +218,18 @@ func middlewareLog(next http.Handler) http.Handler {
})
}
func middlewareAuth(username, password string, next http.Handler) http.Handler {
func isLoopback(remoteAddr string) bool {
return strings.HasPrefix(remoteAddr, "127.") || strings.HasPrefix(remoteAddr, "[::1]") || remoteAddr == "@"
}
func middlewareAuth(username, password string, localAuth bool, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(r.RemoteAddr, "127.") && !strings.HasPrefix(r.RemoteAddr, "[::1]") && r.RemoteAddr != "@" {
if r.Method == "OPTIONS" {
next.ServeHTTP(w, r)
return
}
if localAuth || !isLoopback(r.RemoteAddr) {
user, pass, ok := r.BasicAuth()
if !ok || user != username || pass != password {
w.Header().Set("Www-Authenticate", `Basic realm="go2rtc"`)
@@ -215,6 +247,10 @@ func middlewareCORS(next http.Handler) http.Handler {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
@@ -224,6 +260,8 @@ var mu sync.Mutex
func apiHandler(w http.ResponseWriter, r *http.Request) {
mu.Lock()
app.Info["host"] = r.Host
app.Info["pid"] = os.Getpid()
app.Info["system"] = getSystemInfo()
mu.Unlock()
ResponseJSON(w, app.Info)
@@ -235,6 +273,11 @@ func exitHandler(w http.ResponseWriter, r *http.Request) {
return
}
if IsReadOnly() {
ReadOnlyError(w)
return
}
s := r.URL.Query().Get("code")
code, err := strconv.Atoi(s)
@@ -253,6 +296,11 @@ func restartHandler(w http.ResponseWriter, r *http.Request) {
return
}
if IsReadOnly() {
ReadOnlyError(w)
return
}
path, err := os.Executable()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -271,6 +319,10 @@ func logHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/jsonlines")
_, _ = app.MemoryLog.WriteTo(w)
case "DELETE":
if IsReadOnly() {
ReadOnlyError(w)
return
}
app.MemoryLog.Reset()
Response(w, "OK", "text/plain")
default:
+87 -26
View File
@@ -1,11 +1,15 @@
package api
import (
"errors"
"io"
"maps"
"net/http"
"os"
"slices"
"github.com/AlexxIT/go2rtc/internal/app"
pkgyaml "github.com/AlexxIT/go2rtc/pkg/yaml"
"gopkg.in/yaml.v3"
)
@@ -26,6 +30,10 @@ func configHandler(w http.ResponseWriter, r *http.Request) {
Response(w, data, "application/yaml")
case "POST", "PATCH":
if IsReadOnly() {
ReadOnlyError(w)
return
}
data, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
@@ -55,47 +63,100 @@ func configHandler(w http.ResponseWriter, r *http.Request) {
}
func mergeYAML(file1 string, yaml2 []byte) ([]byte, error) {
// Read the contents of the first YAML file
data1, err := os.ReadFile(file1)
if err != nil {
return nil, err
}
// Unmarshal the first YAML file into a map
var config1 map[string]any
if err = yaml.Unmarshal(data1, &config1); err != nil {
var patch map[string]any
if err = yaml.Unmarshal(yaml2, &patch); err != nil {
return nil, err
}
// Unmarshal the second YAML document into a map
var config2 map[string]any
if err = yaml.Unmarshal(yaml2, &config2); err != nil {
data1, err = mergeYAMLMap(data1, nil, patch)
if err != nil {
return nil, err
}
// Merge the two maps
config1 = merge(config1, config2)
// validate config after merge
if err = yaml.Unmarshal(data1, map[string]any{}); err != nil {
return nil, err
}
// Marshal the merged map into YAML
return yaml.Marshal(&config1)
return data1, nil
}
func merge(dst, src map[string]any) map[string]any {
for k, v := range src {
if vv, ok := dst[k]; ok {
switch vv := vv.(type) {
case map[string]any:
v := v.(map[string]any)
dst[k] = merge(vv, v)
case []any:
v := v.([]any)
dst[k] = v
default:
dst[k] = v
// mergeYAMLMap recursively applies patch values onto config bytes.
func mergeYAMLMap(data []byte, path []string, patch map[string]any) ([]byte, error) {
for _, key := range slices.Sorted(maps.Keys(patch)) {
value := patch[key]
currPath := append(append([]string(nil), path...), key)
if valueMap, ok := value.(map[string]any); ok {
isMap, exists, err := pathIsMapping(data, currPath)
if err != nil {
return nil, err
}
} else {
dst[k] = v
if exists && isMap {
data, err = mergeYAMLMap(data, currPath, valueMap)
} else {
data, err = pkgyaml.Patch(data, currPath, valueMap)
}
if err != nil {
return nil, err
}
continue
}
var err error
data, err = pkgyaml.Patch(data, currPath, value)
if err != nil {
return nil, err
}
}
return dst
return data, nil
}
// pathIsMapping reports whether path exists and ends with a mapping node.
func pathIsMapping(data []byte, path []string) (isMap, exists bool, err error) {
var root yaml.Node
if err = yaml.Unmarshal(data, &root); err != nil {
return false, false, err
}
if len(root.Content) == 0 {
return false, false, nil
}
if len(root.Content) != 1 || root.Content[0].Kind != yaml.MappingNode {
return false, false, errors.New("yaml: expected mapping document")
}
node := root.Content[0]
for i, part := range path {
idx := -1
for j := 0; j < len(node.Content); j += 2 {
if node.Content[j].Value == part {
idx = j
break
}
}
if idx < 0 {
return false, false, nil
}
valueNode := node.Content[idx+1]
if i == len(path)-1 {
return valueNode.Kind == yaml.MappingNode, true, nil
}
if valueNode.Kind != yaml.MappingNode {
return false, false, nil
}
node = valueNode
}
return false, false, nil
}
+36
View File
@@ -0,0 +1,36 @@
package api
import (
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
"github.com/AlexxIT/go2rtc/internal/app"
"github.com/stretchr/testify/require"
)
func TestConfigHandlerReadOnly(t *testing.T) {
prevPath := app.ConfigPath
prevReadOnly := ReadOnly
t.Cleanup(func() {
app.ConfigPath = prevPath
ReadOnly = prevReadOnly
})
app.ConfigPath = filepath.Join(t.TempDir(), "config.yaml")
ReadOnly = true
for _, method := range []string{"POST", "PATCH"} {
t.Run(method, func(t *testing.T) {
req := httptest.NewRequest(method, "/api/config", strings.NewReader("log:\n level: info\n"))
w := httptest.NewRecorder()
configHandler(w, req)
require.Equal(t, http.StatusForbidden, w.Code)
require.Contains(t, w.Body.String(), "read-only")
})
}
}
+283
View File
@@ -0,0 +1,283 @@
package api
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
func TestMergeYAMLPreserveCommentedStreamList(t *testing.T) {
base := `streams:
yard:
- #http://1.1.1.1
- #http://2.2.2.2
- http://3.3.3.3
- #http://4.4.4.4
log:
level: trace
`
patch := `log:
api: debug
`
path := filepath.Join(t.TempDir(), "go2rtc.yaml")
require.NoError(t, os.WriteFile(path, []byte(base), 0o644))
out, err := mergeYAML(path, []byte(patch))
require.NoError(t, err)
merged := string(out)
require.Contains(t, merged, "#http://1.1.1.1")
require.Contains(t, merged, "#http://2.2.2.2")
require.Contains(t, merged, "#http://4.4.4.4")
require.Contains(t, merged, "- http://3.3.3.3")
require.NotContains(t, merged, "- null")
require.Contains(t, merged, "api: debug")
var cfg map[string]any
require.NoError(t, yaml.Unmarshal(out, &cfg))
}
func TestMergeYAMLPreserveUnchangedComments(t *testing.T) {
base := `api:
username: admin
streams:
yard:
- #http://1.1.1.1
- http://3.3.3.3
`
patch := `api:
password: secret
`
path := filepath.Join(t.TempDir(), "go2rtc.yaml")
require.NoError(t, os.WriteFile(path, []byte(base), 0o644))
out, err := mergeYAML(path, []byte(patch))
require.NoError(t, err)
merged := string(out)
require.Contains(t, merged, "username: admin")
require.Contains(t, merged, "password: secret")
require.Contains(t, merged, "#http://1.1.1.1")
require.NotContains(t, merged, "- null")
}
func TestMergeYAMLPreserveCommentsAndFormattingAcrossSections(t *testing.T) {
base := `# global config comment
api: # api section comment
username: admin # inline username comment
streams:
# stream comment
yard:
- #http://1.1.1.1
- http://3.3.3.3
log:
format: |
line1
line2
`
patch := `api:
password: "secret value"
ffmpeg:
bin: /usr/bin/ffmpeg
`
path := filepath.Join(t.TempDir(), "go2rtc.yaml")
require.NoError(t, os.WriteFile(path, []byte(base), 0o644))
out, err := mergeYAML(path, []byte(patch))
require.NoError(t, err)
merged := string(out)
require.Contains(t, merged, "# global config comment")
require.Contains(t, merged, "# api section comment")
require.Contains(t, merged, "# inline username comment")
require.Contains(t, merged, "# stream comment")
require.Contains(t, merged, "#http://1.1.1.1")
require.Contains(t, merged, "password: secret value")
require.Contains(t, merged, "format: |")
require.NotContains(t, merged, "- null")
assertOrder(t, merged, "api:", "streams:", "log:", "ffmpeg:")
var cfg map[string]any
require.NoError(t, yaml.Unmarshal(out, &cfg))
require.Equal(t, "admin", cfg["api"].(map[string]any)["username"])
require.Equal(t, "secret value", cfg["api"].(map[string]any)["password"])
require.Equal(t, "/usr/bin/ffmpeg", cfg["ffmpeg"].(map[string]any)["bin"])
}
func TestMergeYAMLPreserveQuotedValuesAndNestedStructure(t *testing.T) {
base := `api:
username: "admin user"
listen: ":1984"
webrtc:
candidates:
- "stun:stun.l.google.com:19302"
streams:
porch:
- "rtsp://cam.local/stream?token=a:b"
- #disabled source
`
patch := `webrtc:
ice_servers:
- urls:
- stun:stun.cloudflare.com:3478
`
path := filepath.Join(t.TempDir(), "go2rtc.yaml")
require.NoError(t, os.WriteFile(path, []byte(base), 0o644))
out, err := mergeYAML(path, []byte(patch))
require.NoError(t, err)
merged := string(out)
require.Contains(t, merged, `username: "admin user"`)
require.Contains(t, merged, `listen: ":1984"`)
require.Contains(t, merged, `"rtsp://cam.local/stream?token=a:b"`)
require.Contains(t, merged, "#disabled source")
require.Contains(t, merged, "ice_servers:")
require.NotContains(t, merged, "- null")
var cfg map[string]any
require.NoError(t, yaml.Unmarshal(out, &cfg))
require.Equal(t, "admin user", cfg["api"].(map[string]any)["username"])
require.Equal(t, ":1984", cfg["api"].(map[string]any)["listen"])
require.NotNil(t, cfg["streams"])
require.NotNil(t, cfg["webrtc"].(map[string]any)["candidates"])
require.NotNil(t, cfg["webrtc"].(map[string]any)["ice_servers"])
}
func TestMergeYAMLPatchLogKeepsCommentedYardEntriesInline(t *testing.T) {
base := `api:
listen: :1984
read_only: false
static_dir: www
log:
level: trace
mcp:
enabled: true
http: true
sse: true
streams:
cam_main:
- https://example.local/stream.m3u8
yard:
- #http://camera.local/disabled-source-a
- #ffmpeg:http://camera.local/disabled-source-b#video=h264
- ffmpeg:yard#video=mjpeg
- #homekit://camera.local/disabled-source-c
- homekit://camera.local/enabled-source
`
patch := `log:
api: debug
`
path := filepath.Join(t.TempDir(), "go2rtc.yaml")
require.NoError(t, os.WriteFile(path, []byte(base), 0o644))
out, err := mergeYAML(path, []byte(patch))
require.NoError(t, err)
merged := string(out)
require.Contains(t, merged, " level: trace")
require.Contains(t, merged, " api: debug")
require.Contains(t, merged, " - #http://camera.local/disabled-source-a")
require.Contains(t, merged, " - #ffmpeg:http://camera.local/disabled-source-b#video=h264")
require.Contains(t, merged, " - #homekit://camera.local/disabled-source-c")
require.NotContains(t, merged, "\n -\n")
var cfg map[string]any
require.NoError(t, yaml.Unmarshal(out, &cfg))
require.Equal(t, "debug", cfg["log"].(map[string]any)["api"])
}
func TestMergeYAMLPatchLogWithTrailingSpaces(t *testing.T) {
// trailing spaces on "- #comment" lines could confuse node parsing
base := "api:\n listen: :1984\nlog:\n level: trace\nstreams:\n yard:\n" +
" - #http://192.168.88.100/long/path/to/resource \n" +
" - #ffmpeg:http://192.168.88.100/path#video=h264 \n" +
" - ffmpeg:yard#video=mjpeg\n"
patch := "log:\n api: debug\n"
path := filepath.Join(t.TempDir(), "go2rtc.yaml")
require.NoError(t, os.WriteFile(path, []byte(base), 0o644))
out, err := mergeYAML(path, []byte(patch))
require.NoError(t, err)
merged := string(out)
require.Contains(t, merged, " - #http://192.168.88.100/long/path/to/resource")
require.Contains(t, merged, " - #ffmpeg:http://192.168.88.100/path#video=h264")
require.NotContains(t, merged, "\n -\n")
}
func TestMergeYAMLPatchLogPreservesLongCommentedURLs(t *testing.T) {
base := "api:\n" +
" listen: :1984\n" +
" read_only: false\n" +
" static_dir: www\n" +
"log:\n" +
" level: trace\n" +
"mcp:\n" +
" enabled: true\n" +
" http: true\n" +
" sse: true\n" +
"streams:\n" +
" sf_i280_us101:\n" +
" - https://wzmedia.dot.ca.gov/D4/N280_at_JCT_101.stream/playlist.m3u8\n" +
" testsrc_h264:\n" +
" - exec:ffmpeg -hide_banner -re -f lavfi -i testsrc=size=320x240:rate=15 -c:v libx264 -preset ultrafast -tune zerolatency -profile:v baseline -pix_fmt yuv420p -crf 28 -f h264 -\n" +
" yard:\n" +
" - #http://192.168.88.100/c17d5873fa8f1ca5e0f94daa46e29343/live/files/high/index.m3u8\n" +
" - #ffmpeg:http://192.168.88.100/c17d5873fa8f1ca5e0f94daa46e29343/live/files/high/index.m3u8#audio=opus/16000#video=h264\n" +
" - ffmpeg:yard#video=mjpeg\n" +
" - #homekit://192.168.88.100:5001?client_id=00000000-0000-0000-0000-000000000001&client_private=0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001&device_id=00:00:00:00:00:01&device_public=0000000000000000000000000000000000000000000000000000000000000001\n" +
" - homekit://192.168.88.100:5001?client_id=00000000-0000-0000-0000-000000000002&client_private=0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002&device_id=00:00:00:00:00:02&device_public=0000000000000000000000000000000000000000000000000000000000000002\n"
patch := "log:\n api: debug\n"
path := filepath.Join(t.TempDir(), "go2rtc.yaml")
require.NoError(t, os.WriteFile(path, []byte(base), 0o644))
out, err := mergeYAML(path, []byte(patch))
require.NoError(t, err)
merged := string(out)
// patch applied
require.Contains(t, merged, " api: debug")
require.Contains(t, merged, " level: trace")
// commented entries must stay on same line as dash
require.Contains(t, merged, " - #http://192.168.88.100/")
require.Contains(t, merged, " - #ffmpeg:http://192.168.88.100/")
require.Contains(t, merged, " - #homekit://192.168.88.100:")
require.NotContains(t, merged, "\n -\n")
// non-commented entries preserved
require.Contains(t, merged, " - ffmpeg:yard#video=mjpeg")
require.Contains(t, merged, " - homekit://192.168.88.100:5001?client_id=00000000-0000-0000-0000-000000000002")
var cfg map[string]any
require.NoError(t, yaml.Unmarshal(out, &cfg))
require.Equal(t, "debug", cfg["log"].(map[string]any)["api"])
}
func assertOrder(t *testing.T, s string, items ...string) {
t.Helper()
last := -1
for _, item := range items {
idx := strings.Index(s, item)
require.NotEqualf(t, -1, idx, "expected %q in output", item)
require.Greaterf(t, idx, last, "expected %q after previous sections", item)
last = idx
}
}
+16
View File
@@ -0,0 +1,16 @@
package api
type systemInfo struct {
CPUUsage float64 `json:"cpu_usage"` // percent 0-100
MemTotal uint64 `json:"mem_total"` // bytes
MemUsed uint64 `json:"mem_used"` // bytes
}
func getSystemInfo() systemInfo {
memTotal, memUsed := getMemoryInfo()
return systemInfo{
CPUUsage: getCPUUsage(),
MemTotal: memTotal,
MemUsed: memUsed,
}
}
+104
View File
@@ -0,0 +1,104 @@
//go:build darwin
package api
import (
"os/exec"
"runtime"
"strconv"
"strings"
"syscall"
"unsafe"
)
func getMemoryInfo() (total, used uint64) {
total = sysctl64("hw.memsize")
if total == 0 {
return 0, 0
}
pageSize, err := syscall.SysctlUint32("hw.pagesize")
if err != nil {
return total, 0
}
freeCount, _ := syscall.SysctlUint32("vm.page_free_count")
purgeableCount, _ := syscall.SysctlUint32("vm.page_purgeable_count")
speculativeCount, _ := syscall.SysctlUint32("vm.page_speculative_count")
// inactive pages not available via sysctl, parse vm_stat
inactiveCount := vmStatPages("Pages inactive")
available := uint64(freeCount+purgeableCount+speculativeCount)*uint64(pageSize) +
inactiveCount*uint64(pageSize)
if available > total {
return total, 0
}
return total, total - available
}
// vmStatPages parses vm_stat output for a specific counter
func vmStatPages(key string) uint64 {
out, err := exec.Command("vm_stat").Output()
if err != nil {
return 0
}
for _, line := range strings.Split(string(out), "\n") {
if strings.HasPrefix(line, key) {
// format: "Pages inactive: 479321."
parts := strings.Split(line, ":")
if len(parts) < 2 {
continue
}
s := strings.TrimSpace(parts[1])
s = strings.TrimSuffix(s, ".")
val, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return 0
}
return val
}
}
return 0
}
func sysctl64(name string) uint64 {
s, err := syscall.Sysctl(name)
if err != nil {
return 0
}
b := []byte(s)
for len(b) < 8 {
b = append(b, 0)
}
return *(*uint64)(unsafe.Pointer(&b[0]))
}
func getCPUUsage() float64 {
s, err := syscall.Sysctl("vm.loadavg")
if err != nil {
return 0
}
raw := []byte(s)
for len(raw) < 24 {
raw = append(raw, 0)
}
// struct loadavg { fixpt_t ldavg[3]; long fscale; }
ldavg0 := *(*uint32)(unsafe.Pointer(&raw[0]))
fscale := *(*int64)(unsafe.Pointer(&raw[16]))
if fscale == 0 {
return 0
}
load1 := float64(ldavg0) / float64(fscale)
numCPU := float64(runtime.NumCPU())
usage := load1 / numCPU * 100
if usage > 100 {
usage = 100
}
return usage
}
+116
View File
@@ -0,0 +1,116 @@
//go:build darwin
package api
import (
"os/exec"
"runtime"
"strconv"
"strings"
"syscall"
"testing"
)
func TestGetMemoryInfo(t *testing.T) {
total, used := getMemoryInfo()
if total == 0 {
t.Fatal("mem_total is 0")
}
if total < 512*1024*1024 {
t.Fatalf("mem_total too small: %d", total)
}
// total should match sysctl64("hw.memsize")
expectedTotal := sysctl64("hw.memsize")
if total != expectedTotal {
t.Errorf("mem_total %d != hw.memsize %d", total, expectedTotal)
}
if used == 0 {
t.Fatal("mem_used is 0")
}
if used > total {
t.Fatalf("mem_used (%d) > mem_total (%d)", used, total)
}
// cross-check: used should be >= wired+active pages (minimum real usage)
pageSize, _ := syscall.SysctlUint32("hw.pagesize")
wired := vmStatPages("Pages wired down")
active := vmStatPages("Pages active")
minUsed := (wired + active) * uint64(pageSize)
if used < minUsed/2 {
t.Errorf("mem_used (%d) is less than half of wired+active (%d)", used, minUsed)
}
avail := total - used
t.Logf("RAM total: %.1f GB, used: %.1f GB, avail: %.1f GB",
float64(total)/1024/1024/1024,
float64(used)/1024/1024/1024,
float64(avail)/1024/1024/1024)
}
func TestGetCPUUsage(t *testing.T) {
usage := getCPUUsage()
// cross-check with sysctl vm.loadavg
out, err := exec.Command("sysctl", "-n", "vm.loadavg").Output()
if err != nil {
t.Fatal("sysctl vm.loadavg:", err)
}
// format: { 4.24 4.57 5.76 } or { 4,24 4,57 5,76 }
s := strings.Trim(string(out), "{ }\n")
fields := strings.Fields(s)
if len(fields) < 1 {
t.Fatal("cannot parse vm.loadavg:", string(out))
}
load1Str := strings.ReplaceAll(fields[0], ",", ".")
load1, err := strconv.ParseFloat(load1Str, 64)
if err != nil {
t.Fatal("parse load1:", err)
}
numCPU := float64(runtime.NumCPU())
expected := load1 / numCPU * 100
if expected > 100 {
expected = 100
}
if usage < 0 || usage > 100 {
t.Fatalf("cpu_usage out of range: %.1f%%", usage)
}
// allow 15% absolute deviation (load average fluctuates between reads)
diff := usage - expected
if diff < 0 {
diff = -diff
}
if diff > 15 {
t.Errorf("cpu_usage %.1f%% deviates from expected %.1f%% (load1=%.2f, cpus=%d) by %.1f%%",
usage, expected, load1, int(numCPU), diff)
}
t.Logf("CPU usage: %.1f%%, expected: %.1f%% (load1=%.2f, cpus=%d)",
usage, expected, load1, int(numCPU))
}
func TestVmStatPages(t *testing.T) {
inactive := vmStatPages("Pages inactive")
if inactive == 0 {
t.Error("Pages inactive returned 0")
}
free := vmStatPages("Pages free")
if free == 0 {
t.Error("Pages free returned 0")
}
bogus := vmStatPages("Pages nonexistent")
if bogus != 0 {
t.Errorf("nonexistent key returned %d", bogus)
}
t.Logf("inactive=%d, free=%d pages", inactive, free)
}
+84
View File
@@ -0,0 +1,84 @@
//go:build linux
package api
import (
"bytes"
"os"
"strconv"
"strings"
)
func getMemoryInfo() (total, used uint64) {
data, err := os.ReadFile("/proc/meminfo")
if err != nil {
return 0, 0
}
var memTotal, memAvailable uint64
for _, line := range strings.Split(string(data), "\n") {
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
val, err := strconv.ParseUint(fields[1], 10, 64)
if err != nil {
continue
}
switch fields[0] {
case "MemTotal:":
memTotal = val * 1024 // kB to bytes
case "MemAvailable:":
memAvailable = val * 1024
}
}
if memTotal > 0 && memAvailable <= memTotal {
return memTotal, memTotal - memAvailable
}
return memTotal, 0
}
// previous CPU times for delta calculation
var prevIdle, prevTotal uint64
func getCPUUsage() float64 {
data, err := os.ReadFile("/proc/stat")
if err != nil {
return 0
}
// first line: cpu user nice system idle iowait irq softirq steal
idx := bytes.IndexByte(data, '\n')
if idx < 0 {
return 0
}
line := string(data[:idx])
fields := strings.Fields(line)
if len(fields) < 5 || fields[0] != "cpu" {
return 0
}
var total, idle uint64
for i := 1; i < len(fields); i++ {
val, err := strconv.ParseUint(fields[i], 10, 64)
if err != nil {
continue
}
total += val
if i == 4 { // idle is the 4th value (index 4 in fields, 1-based field 4)
idle = val
}
}
deltaTotal := total - prevTotal
deltaIdle := idle - prevIdle
prevIdle = idle
prevTotal = total
if deltaTotal == 0 {
return 0
}
return float64(deltaTotal-deltaIdle) / float64(deltaTotal) * 100
}
+11
View File
@@ -0,0 +1,11 @@
//go:build !linux && !darwin && !windows
package api
func getMemoryInfo() (total, used uint64) {
return 0, 0
}
func getCPUUsage() float64 {
return 0
}
+77
View File
@@ -0,0 +1,77 @@
//go:build windows
package api
import (
"syscall"
"unsafe"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
globalMemoryStatusEx = kernel32.NewProc("GlobalMemoryStatusEx")
getSystemTimes = kernel32.NewProc("GetSystemTimes")
)
// MEMORYSTATUSEX structure
type memoryStatusEx struct {
dwLength uint32
dwMemoryLoad uint32
ullTotalPhys uint64
ullAvailPhys uint64
ullTotalPageFile uint64
ullAvailPageFile uint64
ullTotalVirtual uint64
ullAvailVirtual uint64
ullAvailExtendedVirtual uint64
}
func getMemoryInfo() (total, used uint64) {
var ms memoryStatusEx
ms.dwLength = uint32(unsafe.Sizeof(ms))
ret, _, _ := globalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&ms)))
if ret == 0 {
return 0, 0
}
return ms.ullTotalPhys, ms.ullTotalPhys - ms.ullAvailPhys
}
type filetime struct {
dwLowDateTime uint32
dwHighDateTime uint32
}
func (ft filetime) ticks() uint64 {
return uint64(ft.dwHighDateTime)<<32 | uint64(ft.dwLowDateTime)
}
var prevIdleWin, prevTotalWin uint64
func getCPUUsage() float64 {
var idleTime, kernelTime, userTime filetime
ret, _, _ := getSystemTimes.Call(
uintptr(unsafe.Pointer(&idleTime)),
uintptr(unsafe.Pointer(&kernelTime)),
uintptr(unsafe.Pointer(&userTime)),
)
if ret == 0 {
return 0
}
idle := idleTime.ticks()
total := kernelTime.ticks() + userTime.ticks() // kernel includes idle
deltaTotal := total - prevTotalWin
deltaIdle := idle - prevIdleWin
prevIdleWin = idle
prevTotalWin = total
if deltaTotal == 0 {
return 0
}
return float64(deltaTotal-deltaIdle) / float64(deltaTotal) * 100
}
+69
View File
@@ -0,0 +1,69 @@
# WebSocket
Endpoint: `/api/ws`
Query parameters:
- `src` (required) - Stream name
### WebRTC
Request SDP:
```json
{"type":"webrtc/offer","value":"v=0\r\n..."}
```
Response SDP:
```json
{"type":"webrtc/answer","value":"v=0\r\n..."}
```
Request/response candidate:
- empty value also allowed and optional
```json
{"type":"webrtc/candidate","value":"candidate:3277516026 1 udp 2130706431 192.168.1.123 54321 typ host"}
```
### MSE
Request:
- codecs list optional
```json
{"type":"mse","value":"avc1.640029,avc1.64002A,avc1.640033,hvc1.1.6.L153.B0,mp4a.40.2,mp4a.40.5,flac,opus"}
```
Response:
```json
{"type":"mse","value":"video/mp4; codecs=\"avc1.64001F,mp4a.40.2\""}
```
### HLS
Request:
```json
{"type":"hls","value":"avc1.640029,avc1.64002A,avc1.640033,hvc1.1.6.L153.B0,mp4a.40.2,mp4a.40.5,flac"}
```
Response:
- you MUST rewrite full HTTP path to `http://192.168.1.123:1984/api/hls/playlist.m3u8`
```json
{"type":"hls","value":"#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1000000,CODECS=\"avc1.64001F,mp4a.40.2\"\nhls/playlist.m3u8?id=DvmHdd9w"}
```
### MJPEG
Request/response:
```json
{"type":"mjpeg"}
```
+2 -2
View File
@@ -11,7 +11,7 @@ import (
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/internal/app"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/creds"
"github.com/gorilla/websocket"
"github.com/rs/zerolog"
)
@@ -133,7 +133,7 @@ func apiWS(w http.ResponseWriter, r *http.Request) {
if handler := wsHandlers[msg.Type]; handler != nil {
go func() {
if err = handler(tr, msg); err != nil {
errMsg := core.StripUserinfo(err.Error())
errMsg := creds.SecretString(err.Error())
tr.Write(&Message{Type: "error", Value: msg.Type + ": " + errMsg})
}
}()
+44 -17
View File
@@ -1,17 +1,23 @@
- By default go2rtc will search config file `go2rtc.yaml` in current work directory
- go2rtc support multiple config files:
- `go2rtc -c config1.yaml -c config2.yaml -c config3.yaml`
- go2rtc support inline config as multiple formats from command line:
# App
The application module is responsible for reading configuration files, running other modules and setting up [logs](#log).
The configuration can be edited through the application's WebUI with code highlighting, syntax and specification checking.
- By default, go2rtc will search for the `go2rtc.yaml` config file in the current working directory
- go2rtc supports multiple config files:
- `go2rtc -c config1.yaml -c config2.yaml -c config3.yaml`
- go2rtc supports inline config in multiple formats from the command line:
- **YAML**: `go2rtc -c '{log: {format: text}}'`
- **JSON**: `go2rtc -c '{"log":{"format":"text"}}'`
- **key=value**: `go2rtc -c log.format=text`
- Every next config will overwrite previous (but only defined params)
- Each subsequent config will overwrite the previous one (but only for defined params)
```
go2rtc -config "{log: {format: text}}" -config /config/go2rtc.yaml -config "{rtsp: {listen: ''}}" -config /usr/local/go2rtc/go2rtc.yaml
```
or simple version
or a simpler version
```
go2rtc -c log.format=text -c /config/go2rtc.yaml -c rtsp.listen='' -c /usr/local/go2rtc/go2rtc.yaml
@@ -19,7 +25,7 @@ go2rtc -c log.format=text -c /config/go2rtc.yaml -c rtsp.listen='' -c /usr/local
## Environment variables
There is support for loading external variables into the config. First, they will be attempted to be loaded from [credential files](https://systemd.io/CREDENTIALS). If `CREDENTIALS_DIRECTORY` is not set, then the key will be loaded from an environment variable. If no environment variable is set, then the string will be left as-is.
There is support for loading external variables into the config. First, they will be loaded from [credential files](https://systemd.io/CREDENTIALS). If `CREDENTIALS_DIRECTORY` is not set, then the key will be loaded from an environment variable. If no environment variable is set, then the string will be left as-is.
```yaml
streams:
@@ -32,10 +38,16 @@ rtsp:
## JSON Schema
Editors like [GoLand](https://www.jetbrains.com/go/) and [VS Code](https://code.visualstudio.com/) supports autocomplete and syntax validation.
Editors like [GoLand](https://www.jetbrains.com/go/) and [VS Code](https://code.visualstudio.com/) support autocomplete and syntax validation.
```yaml
# yaml-language-server: $schema=https://raw.githubusercontent.com/AlexxIT/go2rtc/master/website/schema.json
# yaml-language-server: $schema=https://raw.githubusercontent.com/AlexxIT/go2rtc/master/www/schema.json
```
or from a running go2rtc:
```yaml
# yaml-language-server: $schema=http://localhost:1984/schema.json
```
## Defaults
@@ -45,26 +57,41 @@ Editors like [GoLand](https://www.jetbrains.com/go/) and [VS Code](https://code.
```yaml
api:
listen: ":1984"
listen: ":1984" # default public port for WebUI and HTTP API
ffmpeg:
bin: "ffmpeg"
bin: "ffmpeg" # default binary path for FFmpeg
log:
format: "color"
level: "info"
level: "info" # default log level
output: "stdout"
time: "UNIXMS"
rtsp:
listen: ":8554"
listen: ":8554" # default public port for RTSP server
default_query: "video&audio"
srtp:
listen: ":8443"
listen: ":8443" # default public port for SRTP server (used for HomeKit)
webrtc:
listen: ":8555/tcp"
listen: ":8555" # default public port for WebRTC server (TCP and UDP)
ice_servers:
- urls: [ "stun:stun.l.google.com:19302" ]
- urls: [ "stun:stun.cloudflare.com:3478", "stun:stun.l.google.com:19302" ]
```
## Log
You can set different log levels for different modules.
```yaml
log:
format: "" # empty (default, autodetect color support), color, json, text
level: "info" # disabled, trace, debug, info (default), warn, error
output: "stdout" # empty (only to memory), stderr, stdout (default)
time: "UNIXMS" # empty (disable timestamp), UNIXMS (default), UNIXMICRO, UNIXNANO
api: trace # module name: log level
```
Modules: `api`, `streams`, `rtsp`, `webrtc`, `mp4`, `hls`, `mjpeg`, `hass`, `homekit`, `onvif`, `rtmp`, `webtorrent`, `wyoming`, `echo`, `exec`, `expr`, `ffmpeg`, `wyze`, `xiaomi`.
+22 -1
View File
@@ -11,6 +11,7 @@ import (
var (
Version string
Modules []string
UserAgent string
ConfigPath string
Info = make(map[string]any)
@@ -76,6 +77,16 @@ func Init() {
if ConfigPath != "" {
Logger.Info().Str("path", ConfigPath).Msg("config")
}
var cfg struct {
Mod struct {
Modules []string `yaml:"modules"`
} `yaml:"app"`
}
LoadConfig(&cfg)
Modules = cfg.Mod.Modules
}
func readRevisionTime() (revision, vcsTime string) {
@@ -92,10 +103,20 @@ func readRevisionTime() (revision, vcsTime string) {
vcsTime = setting.Value
case "vcs.modified":
if setting.Value == "true" {
revision = "mod." + revision
revision += ".dirty"
}
}
}
// Check version from -buildvcs info
// Format for tagged version : v1.9.13
// Format for modified code: v1.9.14-0.20251215184105-753d6617ab58+dirty
if info.Main.Version != "v"+Version {
// Format: 1.9.13+dev.753d661[.dirty]
// Compatible with "awesomeversion" and "packaging.version" from python.
// Version will be larger than the previous release, but smaller than the next release.
Version += "+dev." + revision
}
}
return
}
+8 -2
View File
@@ -7,7 +7,7 @@ import (
"strings"
"sync"
"github.com/AlexxIT/go2rtc/pkg/shell"
"github.com/AlexxIT/go2rtc/pkg/creds"
"github.com/AlexxIT/go2rtc/pkg/yaml"
)
@@ -20,11 +20,15 @@ func LoadConfig(v any) {
}
var configMu sync.Mutex
var ConfigReadOnly bool
func PatchConfig(path []string, value any) error {
if ConfigPath == "" {
return errors.New("config file disabled")
}
if ConfigReadOnly {
return errors.New("config is read-only")
}
configMu.Lock()
defer configMu.Unlock()
@@ -71,13 +75,15 @@ func initConfig(confs flagConfig) {
// config as file
if ConfigPath == "" {
ConfigPath = conf
initStorage()
}
if data, _ = os.ReadFile(conf); data == nil {
continue
}
data = []byte(shell.ReplaceEnvVars(string(data)))
loadEnv(data)
data = creds.ReplaceVars(data)
configs = append(configs, data)
}
}
+29
View File
@@ -0,0 +1,29 @@
package app
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
func TestPatchConfigReadOnly(t *testing.T) {
prevPath := ConfigPath
prevReadOnly := ConfigReadOnly
t.Cleanup(func() {
ConfigPath = prevPath
ConfigReadOnly = prevReadOnly
})
dir := t.TempDir()
path := filepath.Join(dir, "config.yaml")
require.NoError(t, os.WriteFile(path, []byte(""), 0644))
ConfigPath = path
ConfigReadOnly = true
err := PatchConfig([]string{"streams", "cam"}, "rtsp://example.com")
require.Error(t, err)
require.EqualError(t, err, "config is read-only")
}
+5
View File
@@ -6,6 +6,7 @@ import (
"strings"
"sync"
"github.com/AlexxIT/go2rtc/pkg/creds"
"github.com/mattn/go-isatty"
"github.com/rs/zerolog"
)
@@ -13,6 +14,8 @@ import (
var MemoryLog = newBuffer()
func GetLogger(module string) zerolog.Logger {
Logger.Trace().Str("module", module).Msgf("[log] init")
if s, ok := modules[module]; ok {
lvl, err := zerolog.ParseLevel(s)
if err == nil {
@@ -88,6 +91,8 @@ func initLogger() {
writer = MemoryLog
}
writer = creds.SecretWriter(writer)
lvl, _ := zerolog.ParseLevel(modules["level"])
Logger = zerolog.New(writer).Level(lvl)
+56
View File
@@ -0,0 +1,56 @@
package app
import (
"sync"
"github.com/AlexxIT/go2rtc/pkg/creds"
"github.com/AlexxIT/go2rtc/pkg/yaml"
)
func initStorage() {
storage = &envStorage{data: make(map[string]string)}
creds.SetStorage(storage)
}
func loadEnv(data []byte) {
var cfg struct {
Env map[string]string `yaml:"env"`
}
if err := yaml.Unmarshal(data, &cfg); err != nil {
return
}
storage.mu.Lock()
for name, value := range cfg.Env {
storage.data[name] = value
creds.AddSecret(value)
}
storage.mu.Unlock()
}
var storage *envStorage
type envStorage struct {
data map[string]string
mu sync.Mutex
}
func (s *envStorage) SetValue(name, value string) error {
if err := PatchConfig([]string{"env", name}, value); err != nil {
return err
}
s.mu.Lock()
s.data[name] = value
s.mu.Unlock()
return nil
}
func (s *envStorage) GetValue(name string) (value string, ok bool) {
s.mu.Lock()
value, ok = s.data[name]
s.mu.Unlock()
return
}
+15
View File
@@ -0,0 +1,15 @@
# Bubble
[`new in v1.6.1`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.6.1)
Private format in some cameras from [dvr163.com](http://help.dvr163.com/) and [eseecloud.com](http://www.eseecloud.com/).
## Configuration
- you can skip `username`, `password`, `port`, `ch` and `stream` if they are default
- set up separate streams for different channels and streams
```yaml
streams:
camera1: bubble://username:password@192.168.1.123:34567/bubble/live?ch=0&stream=0
```
+3
View File
@@ -0,0 +1,3 @@
# Debug
This module provides `GET /api/stack`, with which you can find hanging goroutines
+2 -2
View File
@@ -29,8 +29,8 @@ var stackSkip = [][]byte{
[]byte("created by github.com/AlexxIT/go2rtc/internal/homekit.Init"),
// webrtc/api.go
[]byte("created by github.com/pion/ice/v2.NewTCPMuxDefault"),
[]byte("created by github.com/pion/ice/v2.NewUDPMuxDefault"),
[]byte("created by github.com/pion/ice/v4.NewTCPMuxDefault"),
[]byte("created by github.com/pion/ice/v4.NewUDPMuxDefault"),
}
func stackHandler(w http.ResponseWriter, r *http.Request) {
+21
View File
@@ -0,0 +1,21 @@
# Doorbird
[`new in v1.9.8`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.9.8)
This source type supports [Doorbird](https://www.doorbird.com/) devices including MJPEG stream, audio stream as well as two-way audio.
It is recommended to create a separate user within your doorbird setup for go2rtc. Minimum permissions for the user are:
- Watch always
- API operator
## Configuration
```yaml
streams:
doorbird1:
- rtsp://admin:password@192.168.1.123:8557/mpeg/720p/media.amp # RTSP stream
- doorbird://admin:password@192.168.1.123?media=video # MJPEG stream
- doorbird://admin:password@192.168.1.123?media=audio # audio stream
- doorbird://admin:password@192.168.1.123 # two-way audio
```
+21
View File
@@ -0,0 +1,21 @@
# DVR-IP
[`new in v1.2.0`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.2.0)
Private format from DVR-IP NVR, NetSurveillance, Sofia protocol (NETsurveillance ActiveX plugin XMeye SDK).
## Configuration
- you can skip `username`, `password`, `port`, `channel` and `subtype` if they are default
- set up separate streams for different channels
- use `subtype=0` for Main stream, and `subtype=1` for Extra1 stream
- only the TCP protocol is supported
```yaml
streams:
only_stream: dvrip://username:password@192.168.1.123:34567?channel=0&subtype=0
only_tts: dvrip://username:password@192.168.1.123:34567?backchannel=1
two_way_audio:
- dvrip://username:password@192.168.1.123:34567?channel=0&subtype=0
- dvrip://username:password@192.168.1.123:34567?backchannel=1
```
+1 -1
View File
@@ -80,7 +80,7 @@ func sendBroadcasts(conn *net.UDPConn) {
IP: net.IP{255, 255, 255, 255},
}
for i := 0; i < 3; i++ {
for range 3 {
time.Sleep(100 * time.Millisecond)
_, _ = conn.WriteToUDP(data, addr)
}
+48
View File
@@ -0,0 +1,48 @@
# Echo
Some sources may have a dynamic link. And you will need to get it using a Bash or Python script. Your script should echo a link to the source. RTSP, FFmpeg or any of the supported sources.
**Docker** and **Home Assistant add-on** users have preinstalled `python3`, `curl`, `jq`.
## Configuration
```yaml
streams:
apple_hls: echo:python3 hls.py https://developer.apple.com/streaming/examples/basic-stream-osx-ios5.html
```
## Install python libraries
**Docker** and **Hass Add-on** users have preinstalled `python3` without any additional libraries, like [requests](https://requests.readthedocs.io/) or others. If you need some additional libraries - you need to install them to folder with your script:
1. Install [SSH & Web Terminal](https://github.com/hassio-addons/addon-ssh)
2. Goto Add-on Web UI
3. Install library: `pip install requests -t /config/echo`
4. Add your script to `/config/echo/myscript.py`
5. Use your script as source `echo:python3 /config/echo/myscript.py`
## Example: Apple HLS
```yaml
streams:
apple_hls: echo:python3 hls.py https://developer.apple.com/streaming/examples/basic-stream-osx-ios5.html
```
**hls.py**
```python
import re
import sys
from urllib.parse import urljoin
from urllib.request import urlopen
html = urlopen(sys.argv[1]).read().decode("utf-8")
url = re.search(r"https.+?m3u8", html)[0]
html = urlopen(url).read().decode("utf-8")
m = re.search(r"^[a-z0-1/_]+\.m3u8$", html, flags=re.MULTILINE)
url = urljoin(url, m[0])
# ffmpeg:https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/gear1/prog_index.m3u8#video=copy
print("ffmpeg:" + url + "#video=copy")
```
+17
View File
@@ -2,7 +2,9 @@ package echo
import (
"bytes"
"errors"
"os/exec"
"slices"
"github.com/AlexxIT/go2rtc/internal/app"
"github.com/AlexxIT/go2rtc/internal/streams"
@@ -10,11 +12,25 @@ import (
)
func Init() {
var cfg struct {
Mod struct {
AllowPaths []string `yaml:"allow_paths"`
} `yaml:"echo"`
}
app.LoadConfig(&cfg)
allowPaths := cfg.Mod.AllowPaths
log := app.GetLogger("echo")
streams.RedirectFunc("echo", func(url string) (string, error) {
args := shell.QuoteSplit(url[5:])
if allowPaths != nil && !slices.Contains(allowPaths, args[0]) {
return "", errors.New("echo: bin not in allow_paths: " + args[0])
}
b, err := exec.Command(args[0], args[1:]...).Output()
if err != nil {
return "", err
@@ -26,4 +42,5 @@ func Init() {
return string(b), nil
})
streams.MarkInsecure("echo")
}
+12
View File
@@ -0,0 +1,12 @@
# EseeCloud
[`new in v1.9.10`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.9.10)
This source is for cameras with a link like this `http://admin:@192.168.1.123:80/livestream/12`. Related [issue](https://github.com/AlexxIT/go2rtc/issues/1690).
## Configuration
```yaml
streams:
camera1: eseecloud://user:pass@192.168.1.123:80/livestream/12
```
+36
View File
@@ -1,3 +1,39 @@
# Exec
Exec source can run any external application and expect data from it. Two transports are supported - **pipe** ([`new in v1.5.0`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.5.0)) and **RTSP**.
If you want to use **RTSP** transport, the command must contain the `{output}` argument in any place. On launch, it will be replaced by the local address of the RTSP server.
**pipe** reads data from app stdout in different formats: **MJPEG**, **H.264/H.265 bitstream**, **MPEG-TS**. Also pipe can write data to app stdin in two formats: **PCMA** and **PCM/48000**.
The source can be used with:
- [FFmpeg](https://ffmpeg.org/) - go2rtc ffmpeg source is just a shortcut to exec source
- [FFplay](https://ffmpeg.org/ffplay.html) - play audio on your server
- [GStreamer](https://gstreamer.freedesktop.org/)
- [Raspberry Pi Cameras](https://www.raspberrypi.com/documentation/computers/camera_software.html)
- any of your own software
## Configuration
Pipe commands support parameters (format: `exec:{command}#{param1}#{param2}`):
- `killsignal` - signal which will be sent to stop the process (numeric form)
- `killtimeout` - time in seconds for forced termination with sigkill
- `backchannel` - enable backchannel for two-way audio
- `starttimeout` - time in seconds for waiting first byte from RTSP
```yaml
streams:
stream: exec:ffmpeg -re -i /media/BigBuckBunny.mp4 -c copy -rtsp_transport tcp -f rtsp {output}
picam_h264: exec:libcamera-vid -t 0 --inline -o -
picam_mjpeg: exec:libcamera-vid -t 0 --codec mjpeg -o -
pi5cam_h264: exec:libcamera-vid -t 0 --libav-format h264 -o -
canon: exec:gphoto2 --capture-movie --stdout#killsignal=2#killtimeout=5
play_pcma: exec:ffplay -fflags nobuffer -f alaw -ar 8000 -i -#backchannel=1
play_pcm48k: exec:ffplay -fflags nobuffer -f s16be -ar 48000 -i -#backchannel=1
```
## Backchannel
- You can check audio card names in the **Go2rtc > WebUI > Add**
+31 -5
View File
@@ -9,6 +9,7 @@ import (
"io"
"net/url"
"os"
"slices"
"strings"
"sync"
"syscall"
@@ -26,6 +27,16 @@ import (
)
func Init() {
var cfg struct {
Mod struct {
AllowPaths []string `yaml:"allow_paths"`
} `yaml:"exec"`
}
app.LoadConfig(&cfg)
allowPaths = cfg.Mod.AllowPaths
rtsp.HandleFunc(func(conn *pkg.Conn) bool {
waitersMu.Lock()
waiter := waiters[conn.URL.Path]
@@ -45,10 +56,13 @@ func Init() {
})
streams.HandleFunc("exec", execHandle)
streams.MarkInsecure("exec")
log = app.GetLogger("exec")
}
var allowPaths []string
func execHandle(rawURL string) (prod core.Producer, err error) {
rawURL, rawQuery, _ := strings.Cut(rawURL, "#")
query := streams.ParseQuery(rawQuery)
@@ -73,6 +87,11 @@ func execHandle(rawURL string) (prod core.Producer, err error) {
debug: log.Debug().Enabled(),
}
if allowPaths != nil && !slices.Contains(allowPaths, cmd.Args[0]) {
_ = cmd.Close()
return nil, errors.New("exec: bin not in allow_paths: " + cmd.Args[0])
}
if s := query.Get("killsignal"); s != "" {
sig := syscall.Signal(core.Atoi(s))
cmd.Cancel = func() error {
@@ -89,10 +108,17 @@ func execHandle(rawURL string) (prod core.Producer, err error) {
return pcm.NewBackchannel(cmd, query.Get("audio"))
}
var timeout time.Duration
if s := query.Get("starttimeout"); s != "" {
timeout = time.Duration(core.Atoi(s)) * time.Second
} else {
timeout = 30 * time.Second
}
if path == "" {
prod, err = handlePipe(rawURL, cmd)
} else {
prod, err = handleRTSP(rawURL, cmd, path)
prod, err = handleRTSP(rawURL, cmd, path, timeout)
}
if err != nil {
@@ -141,7 +167,7 @@ func handlePipe(source string, cmd *shell.Command) (core.Producer, error) {
return prod, nil
}
func handleRTSP(source string, cmd *shell.Command, path string) (core.Producer, error) {
func handleRTSP(source string, cmd *shell.Command, path string, timeout time.Duration) (core.Producer, error) {
if log.Trace().Enabled() {
cmd.Stdout = os.Stdout
}
@@ -167,11 +193,11 @@ func handleRTSP(source string, cmd *shell.Command, path string) (core.Producer,
return nil, err
}
timeout := time.NewTimer(30 * time.Second)
defer timeout.Stop()
timer := time.NewTimer(timeout)
defer timer.Stop()
select {
case <-timeout.C:
case <-timer.C:
// haven't received data from app in timeout
log.Error().Str("source", source).Msg("[exec] timeout")
return nil, errors.New("exec: timeout")
+74 -12
View File
@@ -1,5 +1,7 @@
# Expr
[`new in v1.8.2`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.8.2)
[Expr](https://github.com/antonmedv/expr) - expression language and expression evaluation for Go.
- [language definition](https://expr.medv.io/docs/Language-Definition) - takes best from JS, Python, Jinja2 syntax
@@ -12,34 +14,94 @@
- `fetch` - JS-like HTTP requests
- `match` - JS-like RegExp queries
## Examples
## Fetch examples
Multiple fetch requests are executed within a single session. They share the same cookie.
**HTTP GET**
```js
var r = fetch('https://example.org/products.json');
```
**HTTP POST JSON**
```js
var r = fetch('https://example.org/post', {
method: 'POST',
// Content-Type: application/json will be set automatically
json: {username: 'example'}
});
```
**HTTP POST Form**
```js
var r = fetch('https://example.org/post', {
method: 'POST',
// Content-Type: application/x-www-form-urlencoded will be set automatically
data: {username: 'example', password: 'password'}
});
```
## Script examples
**Two way audio for Dahua VTO**
```yaml
streams:
dahua_vto: |
expr: let host = "admin:password@192.168.1.123";
fetch("http://"+host+"/cgi-bin/configManager.cgi?action=setConfig&Encode[0].MainFormat[0].Audio.Compression=G.711A&Encode[0].MainFormat[0].Audio.Frequency=8000").ok
? "rtsp://"+host+"/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif" : ""
expr:
let host = 'admin:password@192.168.1.123';
var r = fetch('http://' + host + '/cgi-bin/configManager.cgi?action=setConfig&Encode[0].MainFormat[0].Audio.Compression=G.711A&Encode[0].MainFormat[0].Audio.Frequency=8000');
'rtsp://' + host + '/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif'
```
**dom.ru**
You can get credentials via:
- https://github.com/alexmorbo/domru (file `/share/domru/accounts`)
- https://github.com/ad/domru
You can get credentials from https://github.com/ad/domru
```yaml
streams:
dom_ru: |
expr: let camera = "99999999"; let token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; let operator = 99;
fetch("https://myhome.novotelecom.ru/rest/v1/forpost/cameras/"+camera+"/video", {
headers: {Authorization: "Bearer "+token, Operator: operator}
expr:
let camera = '***';
let token = '***';
let operator = '***';
fetch('https://myhome.proptech.ru/rest/v1/forpost/cameras/' + camera + '/video', {
headers: {
'Authorization': 'Bearer ' + token,
'User-Agent': 'Google sdkgphone64x8664 | Android 14 | erth | 8.26.0 (82600010) | 0 | 0 | 0',
'Operator': operator
}
}).json().data.URL
```
**dom.ufanet.ru**
```yaml
streams:
ufanet_ru: |
expr:
let username = '***';
let password = '***';
let cameraid = '***';
let r1 = fetch('https://ucams.ufanet.ru/api/internal/login/', {
method: 'POST',
data: {username: username, password: password}
});
let r2 = fetch('https://ucams.ufanet.ru/api/v0/cameras/this/?lang=ru', {
method: 'POST',
json: {'fields': ['token_l', 'server'], 'token_l_ttl': 3600, 'numbers': [cameraid]},
}).json().results[0];
'rtsp://' + r2.server.domain + '/' + r2.number + '?token=' + r2.token_l
```
**Parse HLS files from Apple**
Same example in two languages - python and expr.
@@ -76,7 +138,7 @@ streams:
"ffmpeg:" + url3 + "#video=copy"
```
## Comparsion
## Comparison
| expr | python | js |
|------------------------------|----------------------------|--------------------------------|
+1
View File
@@ -25,4 +25,5 @@ func Init() {
return url, nil
})
streams.MarkInsecure("expr")
}
+52 -58
View File
@@ -1,68 +1,62 @@
## FFplay output
# FFmpeg
[FFplay](https://stackoverflow.com/questions/27778678/what-are-mv-fd-aq-vq-sq-and-f-in-a-video-stream) `7.11 A-V: 0.003 fd= 1 aq= 21KB vq= 321KB sq= 0B f=0/0`:
You can get any stream, file or device via FFmpeg and push it to go2rtc. The app will automatically start FFmpeg with the proper arguments when someone starts watching the stream.
- `7.11` - master clock, is the time from start of the stream/video
- `A-V` - av_diff, difference between audio and video timestamps
- `fd` - frames dropped
- `aq` - audio queue (0 - no delay)
- `vq` - video queue (0 - no delay)
- `sq` - subtitle queue
- `f` - timestamp error correction rate (Not 100% sure)
- FFmpeg preinstalled for **Docker** and **Home Assistant add-on** users
- **Home Assistant add-on** users can target files from [/media](https://www.home-assistant.io/more-info/local-media/setup-media/) folder
`M-V`, `M-A` means video stream only, audio stream only respectively.
## Configuration
## Devices Windows
```
>ffmpeg -hide_banner -f dshow -list_options true -i video="VMware Virtual USB Video Device"
[dshow @ 0000025695e52900] DirectShow video device options (from video devices)
[dshow @ 0000025695e52900] Pin "Record" (alternative pin name "0")
[dshow @ 0000025695e52900] pixel_format=yuyv422 min s=1280x720 fps=1 max s=1280x720 fps=10
[dshow @ 0000025695e52900] pixel_format=yuyv422 min s=1280x720 fps=1 max s=1280x720 fps=10 (tv, bt470bg/bt709/unknown, topleft)
[dshow @ 0000025695e52900] pixel_format=nv12 min s=1280x720 fps=1 max s=1280x720 fps=23
[dshow @ 0000025695e52900] pixel_format=nv12 min s=1280x720 fps=1 max s=1280x720 fps=23 (tv, bt470bg/bt709/unknown, topleft)
```
## Devices Mac
```
% ./ffmpeg -hide_banner -f avfoundation -list_devices true -i ""
[AVFoundation indev @ 0x7f8b1f504d80] AVFoundation video devices:
[AVFoundation indev @ 0x7f8b1f504d80] [0] FaceTime HD Camera
[AVFoundation indev @ 0x7f8b1f504d80] [1] Capture screen 0
[AVFoundation indev @ 0x7f8b1f504d80] AVFoundation audio devices:
[AVFoundation indev @ 0x7f8b1f504d80] [0] Soundflower (2ch)
[AVFoundation indev @ 0x7f8b1f504d80] [1] Built-in Microphone
[AVFoundation indev @ 0x7f8b1f504d80] [2] Soundflower (64ch)
```
## Devices Linux
```
# ffmpeg -hide_banner -f v4l2 -list_formats all -i /dev/video0
[video4linux2,v4l2 @ 0x7f7de7c58bc0] Raw : yuyv422 : YUYV 4:2:2 : 640x480 160x120 176x144 320x176 320x240 352x288 432x240 544x288 640x360 752x416 800x448 800x600 864x480 960x544 960x720 1024x576 1184x656 1280x720 1280x960
[video4linux2,v4l2 @ 0x7f7de7c58bc0] Compressed: mjpeg : Motion-JPEG : 640x480 160x120 176x144 320x176 320x240 352x288 432x240 544x288 640x360 752x416 800x448 800x600 864x480 960x544 960x720 1024x576 1184x656 1280x720 1280x960
```
## TTS
Format: `ffmpeg:{input}#{param1}#{param2}#{param3}`. Examples:
```yaml
streams:
tts: ffmpeg:#input=-readrate 1 -readrate_initial_burst 0.001 -f lavfi -i "flite=text='1 2 3 4 5 6 7 8 9 0'"#audio=pcma
# [FILE] all tracks will be copied without transcoding codecs
file1: ffmpeg:/media/BigBuckBunny.mp4
# [FILE] video will be transcoded to H264, audio will be skipped
file2: ffmpeg:/media/BigBuckBunny.mp4#video=h264
# [FILE] video will be copied, audio will be transcoded to PCMU
file3: ffmpeg:/media/BigBuckBunny.mp4#video=copy#audio=pcmu
# [HLS] video will be copied, audio will be skipped
hls: ffmpeg:https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/gear5/prog_index.m3u8#video=copy
# [MJPEG] video will be transcoded to H264
mjpeg: ffmpeg:http://185.97.122.128/cgi-bin/faststream.jpg#video=h264
# [RTSP] video with rotation, should be transcoded, so select H264
rotate: ffmpeg:rtsp://12345678@192.168.1.123/av_stream/ch0#video=h264#rotate=90
```
## Useful links
All transcoding formats have [built-in templates](ffmpeg.go): `h264`, `h265`, `opus`, `pcmu`, `pcmu/16000`, `pcmu/48000`, `pcma`, `pcma/16000`, `pcma/48000`, `aac`, `aac/16000`.
- https://superuser.com/questions/564402/explanation-of-x264-tune
- https://stackoverflow.com/questions/33624016/why-sliced-thread-affect-so-much-on-realtime-encoding-using-ffmpeg-x264
- https://codec.fandom.com/ru/wiki/X264_-_описание_ключей_кодирования
- https://html5test.com/
- https://trac.ffmpeg.org/wiki/Capture/Webcam
- https://trac.ffmpeg.org/wiki/DirectShow
- https://stackoverflow.com/questions/53207692/libav-mjpeg-encoding-and-huffman-table
- https://github.com/tuupola/esp_video/blob/master/README.md
- https://github.com/leandromoreira/ffmpeg-libav-tutorial
- https://www.reddit.com/user/VeritablePornocopium/comments/okw130/ffmpeg_with_libfdk_aac_for_windows_x64/
- https://slhck.info/video/2017/02/24/vbr-settings.html
- [HomeKit audio samples problem](https://superuser.com/questions/1290996/non-monotonous-dts-with-igndts-flag)
But you can override them via YAML config. You can also add your own formats to the config and use them with source params.
```yaml
ffmpeg:
bin: ffmpeg # path to ffmpeg binary
global: "-hide_banner"
timeout: 5 # default timeout in seconds for rtsp inputs
h264: "-codec:v libx264 -g:v 30 -preset:v superfast -tune:v zerolatency -profile:v main -level:v 4.1"
mycodec: "-any args that supported by ffmpeg..."
myinput: "-fflags nobuffer -flags low_delay -timeout {timeout} -i {input}"
myraw: "-ss 00:00:20"
```
- You can use go2rtc stream name as ffmpeg input (ex. `ffmpeg:camera1#video=h264`)
- You can use `video` and `audio` params multiple times (ex. `#video=copy#audio=copy#audio=pcmu`)
- You can use `rotate` param with `90`, `180`, `270` or `-90` values, important with transcoding (ex. `#video=h264#rotate=90`)
- You can use `width` and/or `height` params, important with transcoding (ex. `#video=h264#width=1280`)
- You can use `drawtext` to add a timestamp (ex. `drawtext=x=2:y=2:fontsize=12:fontcolor=white:box=1:boxcolor=black`)
- This will greatly increase the CPU of the server, even with hardware acceleration
- You can use `timeout` param to set RTSP input timeout in seconds (ex. `#timeout=10`)
- You can use `raw` param for any additional FFmpeg arguments (ex. `#raw=-vf transpose=1`)
- You can use `input` param to override default input template (ex. `#input=rtsp/udp` will change RTSP transport from TCP to UDP+TCP)
- You can use raw input value (ex. `#input=-timeout {timeout} -i {input}`)
- You can add your own input templates
Read more about [hardware acceleration](hardware/README.md).
**PS.** It is recommended to check the available hardware in the WebUI add page.
+5
View File
@@ -4,6 +4,7 @@ import (
"net/http"
"strings"
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/internal/streams"
)
@@ -12,6 +13,10 @@ func apiFFmpeg(w http.ResponseWriter, r *http.Request) {
http.Error(w, "", http.StatusMethodNotAllowed)
return
}
if api.IsReadOnly() {
api.ReadOnlyError(w)
return
}
query := r.URL.Query()
dst := query.Get("dst")
+26
View File
@@ -0,0 +1,26 @@
package ffmpeg
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/stretchr/testify/require"
)
func TestApiFFmpegReadOnly(t *testing.T) {
prevReadOnly := api.ReadOnly
t.Cleanup(func() {
api.ReadOnly = prevReadOnly
})
api.ReadOnly = true
req := httptest.NewRequest("POST", "/api/ffmpeg?dst=cam&text=hello", nil)
w := httptest.NewRecorder()
apiFFmpeg(w, req)
require.Equal(t, http.StatusForbidden, w.Code)
}
+22
View File
@@ -0,0 +1,22 @@
# FFmpeg Device
You can get video from any USB camera or Webcam as RTSP or WebRTC stream. This is part of FFmpeg integration.
- check available devices in web interface
- `video_size` and `framerate` must be supported by your camera!
- for Linux supported only video for now
- for macOS you can stream FaceTime camera or whole desktop!
- for macOS important to set right framerate
## Configuration
Format: `ffmpeg:device?{input-params}#{param1}#{param2}#{param3}`
```yaml
streams:
linux_usbcam: ffmpeg:device?video=0&video_size=1280x720#video=h264
windows_webcam: ffmpeg:device?video=0#video=h264
macos_facetime: ffmpeg:device?video=0&audio=1&video_size=1280x720&framerate=30#video=h264#audio=pcma
```
**PS.** It is recommended to check the available devices in the WebUI add page.
+15 -8
View File
@@ -58,15 +58,15 @@ func Init() {
}
var defaults = map[string]string{
"bin": "ffmpeg",
"global": "-hide_banner",
"bin": "ffmpeg",
"global": "-hide_banner",
"timeout": "5",
// inputs
"file": "-re -i {input}",
"http": "-fflags nobuffer -flags low_delay -i {input}",
"rtsp": "-fflags nobuffer -flags low_delay -timeout 5000000 -user_agent go2rtc/ffmpeg -rtsp_flags prefer_tcp -i {input}",
"rtsp/udp": "-fflags nobuffer -flags low_delay -timeout 5000000 -user_agent go2rtc/ffmpeg -i {input}",
"file": "-re -i {input}",
"http": "-fflags nobuffer -flags low_delay -i {input}",
"rtsp": "-fflags nobuffer -flags low_delay -timeout {timeout} -user_agent go2rtc/ffmpeg -rtsp_flags prefer_tcp -i {input}",
"rtsp/udp": "-fflags nobuffer -flags low_delay -timeout {timeout} -user_agent go2rtc/ffmpeg -i {input}",
// output
"output": "-user_agent ffmpeg/go2rtc -rtsp_transport tcp -f rtsp {output}",
@@ -80,7 +80,7 @@ var defaults = map[string]string{
// `-profile high -level 4.1` - most used streaming profile
// `-pix_fmt:v yuv420p` - important for Telegram
"h264": "-c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency -pix_fmt:v yuv420p",
"h265": "-c:v libx265 -g 50 -profile:v main -level:v 5.1 -preset:v superfast -tune:v zerolatency -pix_fmt:v yuv420p",
"h265": "-c:v libx265 -g 50 -profile:v main -x265-params level=5.1:high-tier=0 -preset:v superfast -tune:v zerolatency -pix_fmt:v yuv420p",
"mjpeg": "-c:v mjpeg",
//"mjpeg": "-c:v mjpeg -force_duplicated_matrix:v 1 -huffman:v 0 -pix_fmt:v yuvj420p",
@@ -169,6 +169,13 @@ func inputTemplate(name, s string, query url.Values) string {
} else {
template = defaults[name]
}
if strings.Contains(template, "{timeout}") {
timeout := query.Get("timeout")
if timeout == "" {
timeout = defaults["timeout"]
}
template = strings.Replace(template, "{timeout}", timeout+"000000", 1)
}
return strings.Replace(template, "{input}", s, 1)
}
+5
View File
@@ -123,6 +123,11 @@ func TestParseArgsIpCam(t *testing.T) {
source: "rtmp://example.com#input=rtsp/udp",
expect: `ffmpeg -hide_banner -fflags nobuffer -flags low_delay -timeout 5000000 -user_agent go2rtc/ffmpeg -i rtmp://example.com -c copy -user_agent ffmpeg/go2rtc -rtsp_transport tcp -f rtsp {output}`,
},
{
name: "[RTSP] custom timeout",
source: "rtsp://example.com#timeout=10",
expect: `ffmpeg -hide_banner -allowed_media_types video+audio -fflags nobuffer -flags low_delay -timeout 10000000 -user_agent go2rtc/ffmpeg -rtsp_flags prefer_tcp -i rtsp://example.com -c copy -user_agent ffmpeg/go2rtc -rtsp_transport tcp -f rtsp {output}`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
+27 -27
View File
@@ -2,27 +2,27 @@
You **DON'T** need hardware acceleration if:
- you not using [FFmpeg source](https://github.com/AlexxIT/go2rtc#source-ffmpeg)
- you using only `#video=copy` for FFmpeg source
- you using only `#audio=...` (any audio) transcoding for FFmpeg source
- you're not using the [FFmpeg source](../README.md)
- you're using only `#video=copy` for the FFmpeg source
- you're using only `#audio=...` (any audio) transcoding for the FFmpeg source
You **NEED** hardware acceleration if you using `#video=h264`, `#video=h265`, `#video=mjpeg` (video) transcoding.
You **NEED** hardware acceleration if you're using `#video=h264`, `#video=h265`, `#video=mjpeg` (video) transcoding.
## Important
- Acceleration is disabled by default because it can be unstable (it can be changed in future)
- Acceleration is disabled by default because it can be unstable (this may change in the future)
- go2rtc can automatically detect supported hardware acceleration if enabled
- go2rtc will enable hardware decoding only if hardware encoding supported
- go2rtc will enable hardware decoding only if hardware encoding is supported
- go2rtc will use the same GPU for decoder and encoder
- Intel and AMD will switch to software decoder if input codec is not supported with hardware decoder
- NVidia will fail if input codec is not supported with hardware decoder
- Raspberry always uses software decoder
- Intel and AMD will switch to a software decoder if the input codec isn't supported by the hardware decoder
- NVIDIA will fail if the input codec isn't supported by the hardware decoder
- Raspberry Pi always uses a software decoder
```yaml
streams:
# auto select hardware encoder
camera1_hw: ffmpeg:rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0#video=h264#hardware
# manual select hardware encoder (vaapi, cuda, v4l2m2m, dxva2, videotoolbox)
camera1_vaapi: ffmpeg:rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0#video=h264#hardware=vaapi
```
@@ -31,45 +31,45 @@ streams:
There are two versions of the Docker container and Hass Add-on:
- Latest (alpine) support hardware acceleration for Intel iGPU (CPU with Graphics) and Raspberry.
- Hardware (debian 12) support Intel iGPU, AMD GPU, NVidia GPU.
- Latest (Alpine) supports hardware acceleration for Intel iGPU (CPU with graphics) and Raspberry Pi.
- Hardware (Debian 12) supports Intel iGPU, AMD GPU, NVIDIA GPU.
## Intel iGPU
**Supported on:** Windows binary, Linux binary, Docker, Hass Addon.
If you have Intel CPU Sandy Bridge (2011) with Graphics, you already have support hardware decoding/encoding for `AVC/H.264`.
If you have an Intel Sandy Bridge (2011) CPU with graphics, you already have hardware decoding/encoding support for `AVC/H.264`.
If you have Intel CPU Skylake (2015) with Graphics, you already have support hardware decoding/encoding for `AVC/H.264`, `HEVC/H.265` and `MJPEG`.
If you have an Intel Skylake (2015) CPU with graphics, you already have hardware decoding/encoding support for `AVC/H.264`, `HEVC/H.265` and `MJPEG`.
Read more [here](https://en.wikipedia.org/wiki/Intel_Quick_Sync_Video#Hardware_decoding_and_encoding) and [here](https://en.wikipedia.org/wiki/Intel_Graphics_Technology#Capabilities_(GPU_video_acceleration)).
Linux and Docker:
- It may be important to have the latest version of the OS with the latest version of the Linux kernel. For example, on my **Debian 10 (kernel 4.19)** it did not work, but after update to **Debian 11 (kernel 5.10)** all was fine.
- In case of troube check you have `/dev/dri/` folder on your host.
- It may be important to have a recent OS and Linux kernel. For example, on my **Debian 10 (kernel 4.19)** it did not work, but after updating to **Debian 11 (kernel 5.10)** everything was fine.
- If you run into trouble, check that you have the `/dev/dri/` folder on your host.
Docker users should add `--privileged` option to container for access to Hardware.
Docker users should add the `--privileged` option to the container for access to the hardware.
**PS.** Supported via [VAAPI](https://trac.ffmpeg.org/wiki/Hardware/VAAPI) engine on Linux and [DXVA2+QSV](https://trac.ffmpeg.org/wiki/Hardware/QuickSync) engine on Windows.
## AMD GPU
*I don't have the hardware for test support!!!*
*I don't have the hardware to test this!!!*
**Supported on:** Linux binary, Docker, Hass Addon.
Docker users should install: `alexxit/go2rtc:master-hardware`. Docker users should add `--privileged` option to container for access to Hardware.
Docker users should install: `ghcr.io/AlexxIT/go2rtc:dev-hardware`. Docker users should add the `--privileged` option to the container for access to the hardware.
Hass Addon users should install **go2rtc master hardware** version.
**PS.** Supported via [VAAPI](https://trac.ffmpeg.org/wiki/Hardware/VAAPI) engine.
## NVidia GPU
## NVIDIA GPU
**Supported on:** Windows binary, Linux binary, Docker.
Docker users should install: `alexxit/go2rtc:master-hardware`.
Docker users should install: `ghcr.io/AlexxIT/go2rtc:dev-hardware`.
Read more [here](https://docs.frigate.video/configuration/hardware_acceleration) and [here](https://jellyfin.org/docs/general/administration/hardware-acceleration/#nvidia-hardware-acceleration-on-docker-linux).
@@ -79,11 +79,11 @@ Read more [here](https://docs.frigate.video/configuration/hardware_acceleration)
**Supported on:** Linux binary, Docker, Hass Addon.
I don't recommend using transcoding on the Raspberry Pi 3. It's extreamly slow, even with hardware acceleration. Also it may fail when transcoding 2K+ stream.
I don't recommend using transcoding on the Raspberry Pi 3. It's extremely slow, even with hardware acceleration. Also, it may fail when transcoding a 2K+ stream.
## Raspberry Pi 4
*I don't have the hardware for test support!!!*
*I don't have the hardware to test this!!!*
**Supported on:** Linux binary, Docker, Hass Addon.
@@ -91,16 +91,16 @@ I don't recommend using transcoding on the Raspberry Pi 3. It's extreamly slow,
## macOS
In my tests, transcoding is faster on the M1 CPU than on the M1 GPU. Transcoding time on M1 CPU better than any Intel iGPU and comparable to NVidia RTX 2070.
In my tests, transcoding is faster on the M1 CPU than on the M1 GPU. Transcoding time on the M1 CPU is better than any Intel iGPU and comparable to an NVIDIA RTX 2070.
**PS.** Supported via [videotoolbox](https://trac.ffmpeg.org/wiki/HWAccelIntro#VideoToolbox) engine.
## Rockchip
- Important to use custom FFmpeg with Rockchip support from [@nyanmisaka](https://github.com/nyanmisaka/ffmpeg-rockchip)
- It's important to use a custom FFmpeg build with Rockchip support from [@nyanmisaka](https://github.com/nyanmisaka/ffmpeg-rockchip)
- Static binaries from [@MarcA711](https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/)
- Important to have Linux kernel 5.10 or 6.1
- It's important to have Linux kernel 5.10 or 6.1
**Tested**
- [Orange Pi 3B](https://www.armbian.com/orangepi3b/) with Armbian 6.1, support transcoding H264, H265, MJPEG
- [Orange Pi 3B](https://www.armbian.com/orangepi3b/) with Armbian 6.1, supports transcoding H.264, H.265, MJPEG
+1 -1
View File
@@ -175,7 +175,7 @@ func runToString(bin string, args string) string {
}
func cut(s string, sep byte, pos int) string {
for n := 0; n < pos; n++ {
for range pos {
if i := strings.IndexByte(s, sep); i > 0 {
s = s[i+1:]
} else {
+3 -2
View File
@@ -46,6 +46,7 @@ func NewProducer(url string) (core.Producer, error) {
{Name: core.CodecPCM, ClockRate: 16000},
{Name: core.CodecPCMA, ClockRate: 16000},
{Name: core.CodecPCMU, ClockRate: 16000},
{Name: core.CodecPCML, ClockRate: 8000},
{Name: core.CodecPCM, ClockRate: 8000},
{Name: core.CodecPCMA, ClockRate: 8000},
{Name: core.CodecPCMU, ClockRate: 8000},
@@ -95,11 +96,11 @@ func (p *Producer) newURL() string {
codec := receiver.Codec
switch codec.Name {
case core.CodecOpus:
s += "#audio=opus"
s += "#audio=opus/16000"
case core.CodecAAC:
s += "#audio=aac/16000"
case core.CodecPCML:
s += "#audio=pcml/16000"
s += "#audio=pcml/" + strconv.Itoa(int(codec.ClockRate))
case core.CodecPCM:
s += "#audio=pcm/" + strconv.Itoa(int(codec.ClockRate))
case core.CodecPCMA:
+5
View File
@@ -0,0 +1,5 @@
# Flussonic
[`new in v1.9.10`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.9.10)
Support streams from [Flussonic](https://flussonic.com/) server. Related [issue](https://github.com/AlexxIT/go2rtc/issues/1678).
+7 -3
View File
@@ -1,16 +1,20 @@
# GoPro
[`new in v1.8.3`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.8.3)
Support streaming from [GoPro](https://gopro.com/) cameras, connected via USB or Wi-Fi to Linux, Mac, Windows.
Supported models: HERO9, HERO10, HERO11, HERO12.
Supported OS: Linux, Mac, Windows, [HassOS](https://www.home-assistant.io/installation/)
The other camera models have different APIs. I will try to add them in the next versions.
Other camera models have different APIs. I will try to add them in future versions.
## Config
## Configuration
- USB-connected cameras create a new network interface in the system
- Linux users do not need to install anything
- Windows users should install the [network driver](https://community.gopro.com/s/article/GoPro-Webcam)
- if the camera is detected but the stream does not start - you need to disable firewall
- if the camera is detected but the stream does not start, you need to disable the firewall
1. Discover camera address: WebUI > Add > GoPro
2. Add camera to config
+41
View File
@@ -0,0 +1,41 @@
# Hass
Support import camera links from [Home Assistant](https://www.home-assistant.io/) config files:
- [Generic Camera](https://www.home-assistant.io/integrations/generic/), setup via GUI
- [HomeKit Camera](https://www.home-assistant.io/integrations/homekit_controller/)
- [ONVIF](https://www.home-assistant.io/integrations/onvif/)
- [Roborock](https://github.com/humbertogontijo/homeassistant-roborock) vacuums with camera
## Configuration
```yaml
hass:
config: "/config" # skip this setting if you are a Home Assistant add-on user
streams:
generic_camera: hass:Camera1 # Settings > Integrations > Integration Name
aqara_g3: hass:Camera-Hub-G3-AB12
```
### WebRTC Cameras
[`new in v1.6.0`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.6.0)
Any cameras in WebRTC format are supported. But at the moment Home Assistant only supports some [Nest](https://www.home-assistant.io/integrations/nest/) cameras in this format.
**Important.** The Nest API only allows you to get a link to a stream for 5 minutes.
Do not use this with Frigate! If the stream expires, Frigate will consume all available RAM on your machine within seconds.
It's recommended to use [Nest source](../nest/README.md) - it supports extending the stream.
```yaml
streams:
# link to Home Assistant Supervised
hass-webrtc1: hass://supervisor?entity_id=camera.nest_doorbell
# link to external Home Assistant with Long-Lived Access Tokens
hass-webrtc2: hass://192.168.1.123:8123?entity_id=camera.nest_doorbell&token=eyXYZ...
```
### RTSP Cameras
By default, the Home Assistant API does not allow you to get a dynamic RTSP link to a camera stream. [This method](https://github.com/felipecrs/hass-expose-camera-stream-source#importing-cameras-from-home-assistant-to-go2rtc-or-frigate) can work around it.
+2 -2
View File
@@ -30,10 +30,10 @@ func apiStream(w http.ResponseWriter, r *http.Request) {
// 1. link to go2rtc stream: rtsp://...:8554/{stream_name}
// 2. static link to Hass camera
// 3. dynamic link to Hass camera
if streams.Patch(v.Name, v.Channels.First.Url) != nil {
if _, err := streams.Patch(v.Name, v.Channels.First.Url); err == nil {
apiOK(w, r)
} else {
http.Error(w, "", http.StatusBadRequest)
http.Error(w, err.Error(), http.StatusBadRequest)
}
// /stream/{id}/channel/0/webrtc
+8 -2
View File
@@ -7,6 +7,7 @@ import (
"net/http"
"os"
"path"
"strings"
"sync"
"github.com/AlexxIT/go2rtc/internal/api"
@@ -37,8 +38,13 @@ func Init() {
api.HandleFunc("/streams", apiOK)
api.HandleFunc("/stream/", apiStream)
streams.RedirectFunc("hass", func(url string) (string, error) {
if location := entities[url[5:]]; location != "" {
streams.RedirectFunc("hass", func(rawURL string) (string, error) {
rawURL, rawQuery, _ := strings.Cut(rawURL, "#")
if location := entities[rawURL[5:]]; location != "" {
if rawQuery != "" {
return location + "#" + rawQuery, nil
}
return location, nil
}
+16
View File
@@ -1,3 +1,19 @@
# HLS
[`new in v1.1.0`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.1.0)
[HLS](https://en.wikipedia.org/wiki/HTTP_Live_Streaming) is the worst technology for real-time streaming.
It can only be useful on devices that do not support more modern technology, like [WebRTC](../webrtc/README.md), [MP4](../mp4/README.md).
The go2rtc implementation differs from the standards and may not work with all players.
API examples:
- HLS/TS stream: `http://192.168.1.123:1984/api/stream.m3u8?src=camera1` (H264)
- HLS/fMP4 stream: `http://192.168.1.123:1984/api/stream.m3u8?src=camera1&mp4` (H264, H265, AAC)
Read more about [codecs filters](../../README.md#codecs-filters).
## Useful links
- https://walterebert.com/playground/video/hls/
+1 -1
View File
@@ -11,7 +11,7 @@ import (
)
func handlerWSHLS(tr *ws.Transport, msg *ws.Message) error {
stream := streams.GetOrPatch(tr.Request.URL.Query())
stream, _ := streams.GetOrPatch(tr.Request.URL.Query())
if stream == nil {
return errors.New(api.StreamNotFound)
}
+200
View File
@@ -0,0 +1,200 @@
# Apple HomeKit
This module supports both client and server for the [Apple HomeKit](https://www.apple.com/home-app/accessories/) protocol.
## HomeKit Client
**Important:**
- You can use HomeKit Cameras **without Apple devices** (iPhone, iPad, etc.), it's just a yet another protocol
- HomeKit device can be paired with only one ecosystem. So, if you have paired it to an iPhone (Apple Home), you can't pair it with Home Assistant or go2rtc. Or if you have paired it to go2rtc, you can't pair it with an iPhone
- HomeKit device should be on the same network with working [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS) between the device and go2rtc
go2rtc supports importing paired HomeKit devices from [Home Assistant](../hass/README.md).
So you can use HomeKit camera with Home Assistant and go2rtc simultaneously.
If you are using Home Assistant, I recommend pairing devices with it; it will give you more options.
You can pair device with go2rtc on the HomeKit page. If you can't see your devices, reload the page.
Also, try rebooting your HomeKit device (power off). If you still can't see it, you have a problem with mDNS.
If you see a device but it does not have a pairing button, it is paired to some ecosystem (Apple Home, Home Assistant, HomeBridge, etc.). You need to delete the device from that ecosystem, and it will be available for pairing. If you cannot unpair the device, you will have to reset it.
**Important:**
- HomeKit audio uses very non-standard **AAC-ELD** codec with very non-standard params and specification violations
- Audio can't be played in `VLC` and probably any other player
- Audio should be transcoded for use with MSE, WebRTC, etc.
### Client Configuration
Recommended settings for using HomeKit Camera with WebRTC, MSE, MP4, RTSP:
```yaml
streams:
aqara_g3:
- hass:Camera-Hub-G3-AB12
- ffmpeg:aqara_g3#audio=aac#audio=opus
```
RTSP link with "normal" audio for any player: `rtsp://192.168.1.123:8554/aqara_g3?video&audio=aac`
**This source is in active development!** Tested only with [Aqara Camera Hub G3](https://www.aqara.com/eu/product/camera-hub-g3) (both EU and CN versions).
## HomeKit Server
[`new in v1.7.0`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.7.0)
HomeKit module can work in two modes:
- export any H264 camera to Apple HomeKit
- transparent proxy any Apple HomeKit camera (Aqara, Eve, Eufy, etc.) back to Apple HomeKit, so you will have all camera features in Apple Home and also will have RTSP/WebRTC/MP4/etc. from your HomeKit camera
**Important**
- HomeKit cameras support only H264 video and OPUS audio
### Server Configuration
**Minimal config**
```yaml
streams:
dahua1: rtsp://admin:password@192.168.1.123/cam/realmonitor?channel=1&subtype=0
homekit:
dahua1: # same stream ID from streams list, default PIN - 19550224
```
**Full config**
```yaml
streams:
dahua1:
- rtsp://admin:password@192.168.1.123/cam/realmonitor?channel=1&subtype=0
- ffmpeg:dahua1#video=h264#hardware # if your camera doesn't support H264, important for HomeKit
- ffmpeg:dahua1#audio=opus # only OPUS audio supported by HomeKit
homekit:
dahua1: # same stream ID from streams list
pin: 12345678 # custom PIN, default: 19550224
name: Dahua camera # custom camera name, default: generated from stream ID
device_id: dahua1 # custom ID, default: generated from stream ID
device_private: dahua1 # custom key, default: generated from stream ID
speaker: true # enable 2-way audio (default: false, enable only if camera has a speaker)
```
### HKSV (HomeKit Secure Video)
go2rtc can expose any camera as a HomeKit Secure Video (HKSV) camera. This allows Apple Home to record video clips to iCloud when motion is detected.
**Requirements:**
- Apple Home Hub (Apple TV, HomePod or iPad) on the same network
- iCloud storage plan with HomeKit Secure Video support
- Camera source with H264 video (AAC audio recommended)
**Minimal HKSV config**
```yaml
streams:
outdoor: rtsp://admin:password@192.168.1.123/stream1
homekit:
outdoor:
hksv: true # enable HomeKit Secure Video
motion: continuous # always report motion, Home Hub decides what to record
```
**Full HKSV config**
```yaml
streams:
outdoor:
- rtsp://admin:password@192.168.1.123/stream1
- ffmpeg:outdoor#video=h264#hardware # transcode to H264 if needed
- ffmpeg:outdoor#audio=aac # AAC-LC audio for HKSV recording
homekit:
outdoor:
pin: 12345678
name: Outdoor Camera
hksv: true
motion: api # motion triggered via API
```
**HKSV Doorbell config**
```yaml
homekit:
front_door:
category_id: doorbell
hksv: true
motion: api
```
**Motion modes:**
- `continuous` — MotionDetected is always true; Home Hub continuously receives video and decides what to save. Simplest setup, recommended for most cameras.
- `detect` — automatic motion detection by analyzing H264 P-frame sizes. No external dependencies or CPU-heavy decoding. Works with any H264 source and resolution. Compares each P-frame size against an adaptive baseline using EMA (exponential moving average). When a P-frame exceeds the threshold ratio, motion is triggered with a 30s hold time and 5s cooldown.
- `api` — motion is triggered externally via HTTP API. Use this with Frigate, ONVIF events, or any other motion detection system.
**Motion detect config:**
```yaml
homekit:
outdoor:
hksv: true
motion: detect
motion_threshold: 1.0 # P-frame size / baseline ratio to trigger motion (default: 2.0)
```
The `motion_threshold` controls sensitivity — it's the ratio of P-frame size to the adaptive baseline. When a P-frame exceeds `baseline × threshold`, motion is triggered.
| Scenario | threshold | Notes |
|---|---|---|
| Quiet indoor scene | 1.31.5 | Low noise, stable baseline, even small motion is visible |
| Standard camera (yard, hallway) | 2.0 (default) | Good balance between sensitivity and false positives |
| Outdoor with trees/shadows/wind | 2.53.0 | Wind and shadows produce medium P-frames, need margin |
| Busy street / complex scene | 3.05.0 | Lots of background motion, react only to large events |
Values below 1.0 are meaningless (triggers on every frame). Values above 5.0 require very large motion (person filling half the frame).
**How to tune:** set `log.level: trace` and watch `motion: status` lines — they show current `ratio`. Walk in front of the camera and note the ratio values:
```
motion: status baseline=5000 ratio=0.95 ← quiet
motion: status baseline=5000 ratio=3.21 ← person walked by
motion: status baseline=5000 ratio=1.40 ← shadow/wind
```
Set threshold between "noise" and "real motion". In this example, 2.0 is a good choice (ignores 1.4, catches 3.2).
**Motion API:**
```bash
# Get motion status
curl "http://localhost:1984/api/homekit/motion?id=outdoor"
# → {"id":"outdoor","motion":false}
# Trigger motion start
curl -X POST "http://localhost:1984/api/homekit/motion?id=outdoor"
# Clear motion
curl -X DELETE "http://localhost:1984/api/homekit/motion?id=outdoor"
# Trigger doorbell ring
curl -X POST "http://localhost:1984/api/homekit/doorbell?id=front_door"
```
**Proxy HomeKit camera**
- Video stream from HomeKit camera to Apple device (iPhone, Apple TV) will be transmitted directly
- Video stream from HomeKit camera to RTSP/WebRTC/MP4/etc. will be transmitted via go2rtc
```yaml
streams:
aqara1:
- homekit://...
- ffmpeg:aqara1#audio=aac#audio=opus # optional audio transcoding
homekit:
aqara1: # same stream ID from streams list
```
+83 -37
View File
@@ -3,6 +3,7 @@ package homekit
import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
@@ -14,56 +15,101 @@ import (
"github.com/AlexxIT/go2rtc/pkg/mdns"
)
func apiHandler(w http.ResponseWriter, r *http.Request) {
func apiDiscovery(w http.ResponseWriter, r *http.Request) {
sources, err := discovery()
if err != nil {
api.Error(w, err)
return
}
urls := findHomeKitURLs()
for id, u := range urls {
deviceID := u.Query().Get("device_id")
for _, source := range sources {
if strings.Contains(source.URL, deviceID) {
source.Location = id
break
}
}
}
for _, source := range sources {
if source.Location == "" {
source.Location = " "
}
}
api.ResponseSources(w, sources)
}
func apiHomekit(w http.ResponseWriter, r *http.Request) {
if api.IsReadOnly() && r.Method != "GET" {
api.ReadOnlyError(w)
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
switch r.Method {
case "GET":
sources, err := discovery()
if err != nil {
api.Error(w, err)
return
}
urls := findHomeKitURLs()
for id, u := range urls {
deviceID := u.Query().Get("device_id")
for _, source := range sources {
if strings.Contains(source.URL, deviceID) {
source.Location = id
break
}
if id := r.Form.Get("id"); id != "" {
if srv := servers[id]; srv != nil {
api.ResponsePrettyJSON(w, srv)
} else {
http.Error(w, "server not found", http.StatusNotFound)
}
} else {
api.ResponsePrettyJSON(w, servers)
}
for _, source := range sources {
if source.Location == "" {
source.Location = " "
}
}
api.ResponseSources(w, sources)
case "POST":
if err := r.ParseMultipartForm(1024); err != nil {
api.Error(w, err)
return
}
if err := apiPair(r.Form.Get("id"), r.Form.Get("url")); err != nil {
api.Error(w, err)
id := r.Form.Get("id")
rawURL := r.Form.Get("src") + "&pin=" + r.Form.Get("pin")
if err := apiPair(id, rawURL); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
case "DELETE":
if err := r.ParseMultipartForm(1024); err != nil {
api.Error(w, err)
return
}
if err := apiUnpair(r.Form.Get("id")); err != nil {
api.Error(w, err)
id := r.Form.Get("id")
if err := apiUnpair(id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
}
func apiHomekitAccessories(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
stream := streams.Get(id)
if stream == nil {
http.Error(w, "", http.StatusNotFound)
return
}
rawURL := findHomeKitURL(stream.Sources())
if rawURL == "" {
http.Error(w, "", http.StatusBadRequest)
return
}
client, err := hap.Dial(rawURL)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer client.Close()
res, err := client.Get(hap.PathAccessories)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", api.MimeJSON)
_, _ = io.Copy(w, res.Body)
}
func discovery() ([]*api.Source, error) {
var sources []*api.Source
+37
View File
@@ -0,0 +1,37 @@
package homekit
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/stretchr/testify/require"
)
func TestApiHomekitReadOnly(t *testing.T) {
prevReadOnly := api.ReadOnly
t.Cleanup(func() {
api.ReadOnly = prevReadOnly
})
api.ReadOnly = true
t.Run("POST blocked", func(t *testing.T) {
req := httptest.NewRequest("POST", "/api/homekit", nil)
w := httptest.NewRecorder()
apiHomekit(w, req)
require.Equal(t, http.StatusForbidden, w.Code)
})
t.Run("GET allowed", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/homekit", nil)
w := httptest.NewRecorder()
apiHomekit(w, req)
require.Equal(t, http.StatusOK, w.Code)
})
}
+305 -92
View File
@@ -1,20 +1,26 @@
package homekit
import (
"encoding/json"
"errors"
"io"
"net"
"net/http"
"strings"
"sync"
"time"
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/internal/app"
"github.com/AlexxIT/go2rtc/internal/ffmpeg"
"github.com/AlexxIT/go2rtc/internal/srtp"
"github.com/AlexxIT/go2rtc/internal/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/hap"
"github.com/AlexxIT/go2rtc/pkg/hap/camera"
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
"github.com/AlexxIT/go2rtc/pkg/hksv"
"github.com/AlexxIT/go2rtc/pkg/homekit"
"github.com/AlexxIT/go2rtc/pkg/magic"
"github.com/AlexxIT/go2rtc/pkg/mdns"
"github.com/rs/zerolog"
)
@@ -22,11 +28,18 @@ import (
func Init() {
var cfg struct {
Mod map[string]struct {
Pin string `yaml:"pin"`
Name string `yaml:"name"`
DeviceID string `yaml:"device_id"`
DevicePrivate string `yaml:"device_private"`
Pairings []string `yaml:"pairings"`
Pin string `yaml:"pin"`
Name string `yaml:"name"`
DeviceID string `yaml:"device_id"`
DevicePrivate string `yaml:"device_private"`
CategoryID string `yaml:"category_id"`
Pairings []string `yaml:"pairings"`
HKSV bool `yaml:"hksv"`
Motion string `yaml:"motion"`
MotionThreshold float64 `yaml:"motion_threshold"`
MotionHoldTime float64 `yaml:"motion_hold_time"`
OnvifURL string `yaml:"onvif_url"`
Speaker *bool `yaml:"speaker"`
} `yaml:"homekit"`
}
app.LoadConfig(&cfg)
@@ -35,13 +48,18 @@ func Init() {
streams.HandleFunc("homekit", streamHandler)
api.HandleFunc("api/homekit", apiHandler)
api.HandleFunc("api/homekit", apiHomekit)
api.HandleFunc("api/homekit/accessories", apiHomekitAccessories)
api.HandleFunc("api/homekit/motion", apiMotion)
api.HandleFunc("api/homekit/doorbell", apiDoorbell)
api.HandleFunc("api/discovery/homekit", apiDiscovery)
if cfg.Mod == nil {
return
}
servers = map[string]*server{}
hosts = map[string]*hksv.Server{}
servers = map[string]*hksv.Server{}
var entries []*mdns.ServiceEntry
for id, conf := range cfg.Mod {
@@ -51,78 +69,79 @@ func Init() {
continue
}
if conf.Pin == "" {
conf.Pin = "19550224" // default PIN
var proxyURL string
if url := findHomeKitURL(stream.Sources()); url != "" {
proxyURL = url
}
pin, err := hap.SanitizePin(conf.Pin)
// Remap "onvif" → "api" for hksv.Server; ONVIF watcher drives motion externally.
motionMode := conf.Motion
if motionMode == "onvif" {
motionMode = "api"
}
srv, err := hksv.NewServer(hksv.Config{
StreamName: id,
Pin: conf.Pin,
Name: conf.Name,
DeviceID: conf.DeviceID,
DevicePrivate: conf.DevicePrivate,
CategoryID: conf.CategoryID,
Pairings: conf.Pairings,
ProxyURL: proxyURL,
HKSV: conf.HKSV,
MotionMode: motionMode,
MotionThreshold: conf.MotionThreshold,
Speaker: conf.Speaker,
UserAgent: app.UserAgent,
Version: app.Version,
Streams: &go2rtcStreamProvider{},
Store: &go2rtcPairingStore{},
Snapshots: &go2rtcSnapshotProvider{},
LiveStream: &go2rtcLiveStreamHandler{},
Logger: log,
Port: uint16(api.Port),
})
if err != nil {
log.Error().Err(err).Caller().Send()
log.Error().Err(err).Str("stream", id).Msg("[homekit] create server failed")
continue
}
deviceID := calcDeviceID(conf.DeviceID, id) // random MAC-address
name := calcName(conf.Name, deviceID)
srv := &server{
stream: id,
srtp: srtp.Server,
pairings: conf.Pairings,
}
srv.hap = &hap.Server{
Pin: pin,
DeviceID: deviceID,
DevicePrivate: calcDevicePrivate(conf.DevicePrivate, id),
GetPair: srv.GetPair,
AddPair: srv.AddPair,
Handler: homekit.ServerHandler(srv),
}
if url := findHomeKitURL(stream.Sources()); url != "" {
// 1. Act as transparent proxy for HomeKit camera
dial := func() (net.Conn, error) {
client, err := homekit.Dial(url, srtp.Server)
if err != nil {
return nil, err
}
return client.Conn(), nil
// Start ONVIF motion watcher if configured.
if conf.Motion == "onvif" {
onvifURL := conf.OnvifURL
if onvifURL == "" {
sources := stream.Sources()
log.Debug().Str("stream", id).Strs("sources", sources).
Msg("[homekit] onvif motion: searching for ONVIF URL in stream sources")
onvifURL = findOnvifURL(sources)
}
if onvifURL == "" {
log.Warn().Str("stream", id).Msg("[homekit] onvif motion: no ONVIF URL found, set onvif_url or use onvif:// stream source")
} else {
holdTime := time.Duration(conf.MotionHoldTime) * time.Second
if holdTime <= 0 {
holdTime = 30 * time.Second
}
log.Info().Str("stream", id).Str("onvif_url", onvifURL).
Dur("hold_time", holdTime).Msg("[homekit] starting ONVIF motion watcher")
startOnvifMotionWatcher(srv, onvifURL, holdTime, log)
}
srv.hap.Handler = homekit.ProxyHandler(srv, dial)
} else {
// 2. Act as basic HomeKit camera
srv.accessory = camera.NewAccessory("AlexxIT", "go2rtc", name, "-", app.Version)
srv.hap.Handler = homekit.ServerHandler(srv)
}
srv.mdns = &mdns.ServiceEntry{
Name: name,
Port: uint16(api.Port),
Info: map[string]string{
hap.TXTConfigNumber: "1",
hap.TXTFeatureFlags: "0",
hap.TXTDeviceID: deviceID,
hap.TXTModel: app.UserAgent,
hap.TXTProtoVersion: "1.1",
hap.TXTStateNumber: "1",
hap.TXTStatusFlags: hap.StatusNotPaired,
hap.TXTCategory: hap.CategoryCamera,
hap.TXTSetupHash: srv.hap.SetupHash(),
},
}
entries = append(entries, srv.mdns)
entry := srv.MDNSEntry()
entries = append(entries, entry)
srv.UpdateStatus()
host := entry.Host(mdns.ServiceHAP)
hosts[host] = srv
servers[id] = srv
host := srv.mdns.Host(mdns.ServiceHAP)
servers[host] = srv
log.Trace().Msgf("[homekit] new server: %s", entry)
}
api.HandleFunc(hap.PathPairSetup, hapHandler)
api.HandleFunc(hap.PathPairVerify, hapHandler)
log.Trace().Msgf("[homekit] mdns: %s", entries)
go func() {
if err := mdns.Serve(mdns.ServiceHAP, entries); err != nil {
log.Error().Err(err).Caller().Send()
@@ -131,7 +150,183 @@ func Init() {
}
var log zerolog.Logger
var servers map[string]*server
var hosts map[string]*hksv.Server
var servers map[string]*hksv.Server
// go2rtcStreamProvider implements hksv.StreamProvider
type go2rtcStreamProvider struct{}
func (p *go2rtcStreamProvider) AddConsumer(name string, cons core.Consumer) error {
stream := streams.Get(name)
if stream == nil {
return errors.New("stream not found: " + name)
}
return stream.AddConsumer(cons)
}
func (p *go2rtcStreamProvider) RemoveConsumer(name string, cons core.Consumer) {
if s := streams.Get(name); s != nil {
s.RemoveConsumer(cons)
}
}
// go2rtcPairingStore implements hksv.PairingStore
type go2rtcPairingStore struct{}
func (s *go2rtcPairingStore) SavePairings(name string, pairings []string) error {
return app.PatchConfig([]string{"homekit", name, "pairings"}, pairings)
}
// go2rtcSnapshotProvider implements hksv.SnapshotProvider
type go2rtcSnapshotProvider struct{}
func (s *go2rtcSnapshotProvider) GetSnapshot(streamName string, width, height int) ([]byte, error) {
stream := streams.Get(streamName)
if stream == nil {
return nil, errors.New("stream not found: " + streamName)
}
cons := magic.NewKeyframe()
if err := stream.AddConsumer(cons); err != nil {
return nil, err
}
once := &core.OnceBuffer{}
_, _ = cons.WriteTo(once)
b := once.Buffer()
stream.RemoveConsumer(cons)
switch cons.CodecName() {
case core.CodecH264, core.CodecH265:
var err error
if b, err = ffmpeg.JPEGWithScale(b, width, height); err != nil {
return nil, err
}
}
return b, nil
}
// go2rtcLiveStreamHandler implements hksv.LiveStreamHandler
type go2rtcLiveStreamHandler struct {
mu sync.Mutex
consumers map[string]*homekit.Consumer
lastSessionID string
}
func (h *go2rtcLiveStreamHandler) SetupEndpoints(conn net.Conn, offer *camera.SetupEndpointsRequest) (any, error) {
consumer := homekit.NewConsumer(conn, srtp.Server)
consumer.SetOffer(offer)
old := h.setConsumer(offer.SessionID, consumer)
if old != nil && old != consumer {
_ = old.Stop()
}
answer := consumer.GetAnswer()
v, err := tlv8.MarshalBase64(answer)
if err != nil {
return nil, err
}
return v, nil
}
func (h *go2rtcLiveStreamHandler) GetEndpointsResponse() any {
consumer := h.latestConsumer()
if consumer == nil {
return nil
}
answer := consumer.GetAnswer()
v, _ := tlv8.MarshalBase64(answer)
return v
}
func (h *go2rtcLiveStreamHandler) StartStream(streamName string, conf *camera.SelectedStreamConfiguration, connTracker hksv.ConnTracker) error {
sessionID := conf.Control.SessionID
consumer := h.getConsumer(sessionID)
if consumer == nil {
return errors.New("no consumer")
}
if !consumer.SetConfig(conf) {
return errors.New("wrong config")
}
connTracker.AddConn(consumer)
stream := streams.Get(streamName)
if stream == nil {
connTracker.DelConn(consumer)
return errors.New("stream not found: " + streamName)
}
if err := stream.AddConsumer(consumer); err != nil {
connTracker.DelConn(consumer)
return err
}
go func() {
_, _ = consumer.WriteTo(nil)
stream.RemoveConsumer(consumer)
connTracker.DelConn(consumer)
h.removeConsumer(sessionID, consumer)
}()
return nil
}
func (h *go2rtcLiveStreamHandler) StopStream(sessionID string, connTracker hksv.ConnTracker) error {
consumer := h.getConsumer(sessionID)
if consumer != nil {
_ = consumer.Stop()
h.removeConsumer(sessionID, consumer)
}
return nil
}
func (h *go2rtcLiveStreamHandler) setConsumer(sessionID string, consumer *homekit.Consumer) *homekit.Consumer {
h.mu.Lock()
defer h.mu.Unlock()
if h.consumers == nil {
h.consumers = map[string]*homekit.Consumer{}
}
old := h.consumers[sessionID]
h.consumers[sessionID] = consumer
h.lastSessionID = sessionID
return old
}
func (h *go2rtcLiveStreamHandler) getConsumer(sessionID string) *homekit.Consumer {
h.mu.Lock()
defer h.mu.Unlock()
return h.consumers[sessionID]
}
func (h *go2rtcLiveStreamHandler) latestConsumer() *homekit.Consumer {
h.mu.Lock()
defer h.mu.Unlock()
return h.consumers[h.lastSessionID]
}
func (h *go2rtcLiveStreamHandler) removeConsumer(sessionID string, consumer *homekit.Consumer) {
h.mu.Lock()
defer h.mu.Unlock()
if h.consumers[sessionID] == consumer {
delete(h.consumers, sessionID)
if h.lastSessionID == sessionID {
h.lastSessionID = ""
for id := range h.consumers {
h.lastSessionID = id
break
}
}
}
}
func streamHandler(rawURL string) (core.Producer, error) {
if srtp.Server == nil {
@@ -142,52 +337,33 @@ func streamHandler(rawURL string) (core.Producer, error) {
client, err := homekit.Dial(rawURL, srtp.Server)
if client != nil && rawQuery != "" {
query := streams.ParseQuery(rawQuery)
client.MaxWidth = core.Atoi(query.Get("maxwidth"))
client.MaxHeight = core.Atoi(query.Get("maxheight"))
client.Bitrate = parseBitrate(query.Get("bitrate"))
}
return client, err
}
func resolve(host string) *server {
if len(servers) == 1 {
for _, srv := range servers {
func resolve(host string) *hksv.Server {
if len(hosts) == 1 {
for _, srv := range hosts {
return srv
}
}
if srv, ok := servers[host]; ok {
if srv, ok := hosts[host]; ok {
return srv
}
return nil
}
func hapHandler(w http.ResponseWriter, r *http.Request) {
conn, rw, err := w.(http.Hijacker).Hijack()
if err != nil {
return
}
defer conn.Close()
// Can support multiple HomeKit cameras on single port ONLY for Apple devices.
// Doesn't support Home Assistant and any other open source projects
// because they don't send the host header in requests.
srv := resolve(r.Host)
if srv == nil {
log.Error().Msg("[homekit] unknown host: " + r.Host)
_ = hap.WriteBackoff(rw)
return
}
switch r.RequestURI {
case hap.PathPairSetup:
err = srv.hap.PairSetup(r, rw, conn)
case hap.PathPairVerify:
err = srv.hap.PairVerify(r, rw, conn)
}
if err != nil && err != io.EOF {
log.Error().Err(err).Caller().Send()
}
srv.Handle(w, r)
}
func findHomeKitURL(sources []string) string {
@@ -203,13 +379,50 @@ func findHomeKitURL(sources []string) string {
if strings.HasPrefix(url, "hass") {
location, _ := streams.Location(url)
if strings.HasPrefix(location, "homekit") {
return url
return location
}
}
return ""
}
func apiMotion(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
srv := servers[id]
if srv == nil {
http.Error(w, "server not found: "+id, http.StatusNotFound)
return
}
switch r.Method {
case "GET":
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"id": id,
"motion": srv.MotionDetected(),
})
case "POST":
srv.SetMotionDetected(true)
case "DELETE":
srv.SetMotionDetected(false)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
func apiDoorbell(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
id := r.URL.Query().Get("id")
srv := servers[id]
if srv == nil {
http.Error(w, "server not found: "+id, http.StatusNotFound)
return
}
srv.TriggerDoorbell()
}
func parseBitrate(s string) int {
n := len(s)
if n == 0 {
+287
View File
@@ -0,0 +1,287 @@
package homekit
import (
"strings"
"sync"
"time"
"github.com/AlexxIT/go2rtc/pkg/hksv"
"github.com/AlexxIT/go2rtc/pkg/onvif"
"github.com/rs/zerolog"
)
const (
onvifSubscriptionTimeout = 60 * time.Second
onvifPullTimeout = 30 * time.Second
onvifMessageLimit = 10
onvifRenewMargin = 10 * time.Second
onvifMinReconnectDelay = 5 * time.Second
onvifMaxReconnectDelay = 60 * time.Second
)
type onvifPullPoint interface {
PullMessages(timeout time.Duration, limit int) ([]byte, error)
Renew(timeout time.Duration) error
Unsubscribe() error
}
type onvifPullPointFactory func(rawURL string, timeout time.Duration) (onvifPullPoint, error)
// onvifMotionWatcher subscribes to ONVIF PullPoint events
// and forwards motion state to an hksv.Server.
type onvifMotionWatcher struct {
srv *hksv.Server
onvifURL string
holdTime time.Duration
log zerolog.Logger
now func() time.Time
newPullPoint onvifPullPointFactory
subscriptionTimeout time.Duration
pullTimeout time.Duration
renewMargin time.Duration
messageLimit int
done chan struct{}
once sync.Once
}
func newOnvifMotionWatcher(srv *hksv.Server, onvifURL string, holdTime time.Duration, log zerolog.Logger) *onvifMotionWatcher {
return &onvifMotionWatcher{
srv: srv,
onvifURL: onvifURL,
holdTime: holdTime,
log: log,
now: time.Now,
newPullPoint: newOnvifPullPoint,
subscriptionTimeout: onvifSubscriptionTimeout,
pullTimeout: onvifPullTimeout,
renewMargin: onvifRenewMargin,
messageLimit: onvifMessageLimit,
done: make(chan struct{}),
}
}
// startOnvifMotionWatcher creates and starts a new ONVIF motion watcher.
func startOnvifMotionWatcher(srv *hksv.Server, onvifURL string, holdTime time.Duration, log zerolog.Logger) *onvifMotionWatcher {
w := newOnvifMotionWatcher(srv, onvifURL, holdTime, log)
go w.run()
return w
}
// stop shuts down the watcher goroutine.
func (w *onvifMotionWatcher) stop() {
w.once.Do(func() { close(w.done) })
}
// run is the main loop: create subscription, poll, handle events, reconnect on failure.
func (w *onvifMotionWatcher) run() {
w.log.Debug().Str("url", w.onvifURL).Dur("hold_time", w.holdTime).
Msg("[homekit] onvif motion watcher starting")
delay := onvifMinReconnectDelay
for {
select {
case <-w.done:
w.log.Debug().Msg("[homekit] onvif motion watcher stopped (before connect)")
return
default:
}
w.log.Debug().Str("url", w.onvifURL).Msg("[homekit] onvif motion connecting to camera")
err := w.connectAndPoll()
if err != nil {
w.log.Warn().Err(err).Str("url", w.onvifURL).Msg("[homekit] onvif motion error")
} else {
delay = onvifMinReconnectDelay
}
select {
case <-w.done:
w.log.Debug().Msg("[homekit] onvif motion watcher stopped (after poll)")
return
default:
}
w.log.Debug().Dur("delay", delay).Msg("[homekit] onvif motion reconnecting")
select {
case <-time.After(delay):
case <-w.done:
w.log.Debug().Msg("[homekit] onvif motion watcher stopped (during backoff)")
return
}
delay *= 2
if delay > onvifMaxReconnectDelay {
delay = onvifMaxReconnectDelay
}
}
}
// connectAndPoll creates a subscription and polls for events until an error occurs or stop is called.
func (w *onvifMotionWatcher) connectAndPoll() error {
w.log.Trace().Str("url", w.onvifURL).Dur("timeout", w.subscriptionTimeout).
Msg("[homekit] onvif motion: creating pull point subscription")
sub, err := w.newPullPoint(w.onvifURL, w.subscriptionTimeout)
if err != nil {
w.log.Debug().Err(err).Str("url", w.onvifURL).
Msg("[homekit] onvif motion: pull point creation failed")
return err
}
w.log.Info().Str("url", w.onvifURL).Msg("[homekit] onvif motion subscription created")
defer func() {
w.log.Trace().Msg("[homekit] onvif motion: unsubscribing")
_ = sub.Unsubscribe()
}()
// motionActive tracks whether we've reported motion=true to the HKSV server.
// Hold timer ensures motion stays active for at least holdTime after last trigger,
// regardless of whether the camera sends explicit "motion=false".
// This matches the behavior of the built-in MotionDetector (30s hold time).
motionActive := false
var holdTimer *time.Timer
defer func() {
if holdTimer != nil {
holdTimer.Stop()
}
}()
renewInterval := w.subscriptionRenewInterval()
renewAt := w.now().Add(renewInterval)
w.log.Trace().Dur("renew_interval", renewInterval).
Msg("[homekit] onvif motion: subscription renew scheduled")
pollCount := 0
for {
select {
case <-w.done:
w.log.Debug().Int("polls", pollCount).
Msg("[homekit] onvif motion: poll loop stopped")
return nil
default:
}
if !renewAt.After(w.now()) {
w.log.Trace().Msg("[homekit] onvif motion: renewing subscription")
if err := sub.Renew(w.subscriptionTimeout); err != nil {
w.log.Warn().Err(err).Msg("[homekit] onvif motion: renew failed")
return err
}
renewAt = w.now().Add(renewInterval)
w.log.Trace().Msg("[homekit] onvif motion: subscription renewed")
}
pullTimeout := w.nextPullTimeout(renewAt)
w.log.Trace().Dur("timeout", pullTimeout).Int("limit", w.messageLimit).
Int("poll", pollCount+1).Msg("[homekit] onvif motion: pulling messages")
b, err := sub.PullMessages(pullTimeout, w.messageLimit)
if err != nil {
w.log.Debug().Err(err).Int("polls", pollCount).
Msg("[homekit] onvif motion: pull messages failed")
return err
}
pollCount++
w.log.Trace().Int("bytes", len(b)).Int("poll", pollCount).
Msg("[homekit] onvif motion: pull response received")
if l := w.log.Trace(); l.Enabled() {
l.Str("body", string(b)).Msg("[homekit] onvif motion: raw response")
}
motion, found := onvif.ParseMotionEvents(b)
w.log.Trace().Bool("found", found).Bool("motion", motion).
Bool("active", motionActive).Msg("[homekit] onvif motion: parse result")
if !found {
w.log.Trace().Msg("[homekit] onvif motion: no motion events in response")
continue
}
if motion {
// Motion detected — activate and start/reset hold timer.
if !motionActive {
motionActive = true
w.srv.SetMotionDetected(true)
w.log.Debug().Msg("[homekit] onvif motion: detected")
} else {
w.log.Trace().Msg("[homekit] onvif motion: still active, resetting hold timer")
}
// Reset hold timer on every motion=true event.
if holdTimer != nil {
holdTimer.Stop()
}
holdTimer = time.AfterFunc(w.holdTime, func() {
motionActive = false
w.srv.SetMotionDetected(false)
w.log.Debug().Msg("[homekit] onvif motion: hold expired")
})
} else {
// Camera sent explicit motion=false.
// Do NOT clear immediately — let the hold timer handle it.
// This ensures motion stays active for at least holdTime,
// giving the Home Hub enough time to open the DataStream.
w.log.Debug().Dur("remaining_hold", w.holdTime).
Bool("active", motionActive).
Msg("[homekit] onvif motion: camera reported clear, waiting for hold timer")
}
}
}
func (w *onvifMotionWatcher) subscriptionRenewInterval() time.Duration {
interval := w.subscriptionTimeout - w.renewMargin
if interval <= 0 {
interval = w.subscriptionTimeout / 2
}
if interval <= 0 {
interval = time.Second
}
return interval
}
func (w *onvifMotionWatcher) nextPullTimeout(renewAt time.Time) time.Duration {
timeout := w.pullTimeout
if timeout <= 0 {
timeout = time.Second
}
if untilRenew := renewAt.Sub(w.now()); untilRenew > 0 && untilRenew < timeout {
timeout = untilRenew
}
if timeout <= 0 {
timeout = time.Second
}
return timeout
}
func newOnvifPullPoint(rawURL string, timeout time.Duration) (onvifPullPoint, error) {
client, err := onvif.NewClient(rawURL)
if err != nil {
return nil, err
}
return client.CreatePullPointSubscription(timeout)
}
// findOnvifURL looks for an onvif:// URL in stream sources.
func findOnvifURL(sources []string) string {
for _, src := range sources {
if strings.HasPrefix(src, "onvif://") || strings.HasPrefix(src, "onvif:") {
return src
}
}
return ""
}
+99
View File
@@ -0,0 +1,99 @@
package homekit
import (
"errors"
"testing"
"time"
"github.com/AlexxIT/go2rtc/pkg/hksv"
"github.com/rs/zerolog"
)
func TestOnvifMotionWatcherConnectAndPollRenewsBeforeLeaseExpires(t *testing.T) {
start := time.Unix(0, 0)
now := start
stopErr := errors.New("stop pull loop")
sub := &fakeOnvifPullPoint{
t: t,
now: &now,
pullErrAt: 3,
pullErr: stopErr,
}
w := newOnvifMotionWatcher(&hksv.Server{}, "onvif://camera", 30*time.Second, zerolog.Nop())
w.now = func() time.Time { return now }
w.newPullPoint = func(rawURL string, timeout time.Duration) (onvifPullPoint, error) {
if rawURL != "onvif://camera" {
t.Fatalf("unexpected ONVIF URL: %s", rawURL)
}
if timeout != 60*time.Second {
t.Fatalf("unexpected subscription timeout: %v", timeout)
}
return sub, nil
}
err := w.connectAndPoll()
if !errors.Is(err, stopErr) {
t.Fatalf("expected %v, got %v", stopErr, err)
}
wantPulls := []time.Duration{30 * time.Second, 20 * time.Second, 30 * time.Second}
if len(sub.pullTimeouts) != len(wantPulls) {
t.Fatalf("unexpected pull count: got %d want %d", len(sub.pullTimeouts), len(wantPulls))
}
for i, want := range wantPulls {
if sub.pullTimeouts[i] != want {
t.Fatalf("pull %d timeout mismatch: got %v want %v", i+1, sub.pullTimeouts[i], want)
}
}
if sub.renewCalls != 1 {
t.Fatalf("expected 1 renew call, got %d", sub.renewCalls)
}
if !sub.unsubscribed {
t.Fatal("expected unsubscribe on exit")
}
}
type fakeOnvifPullPoint struct {
t *testing.T
now *time.Time
pullTimeouts []time.Duration
renewCalls int
unsubscribed bool
pullErrAt int
pullErr error
}
func (f *fakeOnvifPullPoint) PullMessages(timeout time.Duration, limit int) ([]byte, error) {
if limit != 10 {
f.t.Fatalf("unexpected message limit: %d", limit)
}
f.pullTimeouts = append(f.pullTimeouts, timeout)
*f.now = f.now.Add(timeout)
if f.pullErrAt > 0 && len(f.pullTimeouts) == f.pullErrAt {
return nil, f.pullErr
}
return []byte(`<tev:PullMessagesResponse xmlns:tev="http://www.onvif.org/ver10/events/wsdl"/>`), nil
}
func (f *fakeOnvifPullPoint) Renew(timeout time.Duration) error {
if timeout != 60*time.Second {
f.t.Fatalf("unexpected renew timeout: %v", timeout)
}
f.renewCalls++
return nil
}
func (f *fakeOnvifPullPoint) Unsubscribe() error {
f.unsubscribed = true
return nil
}
-265
View File
@@ -1,265 +0,0 @@
package homekit
import (
"crypto/ed25519"
"crypto/sha512"
"encoding/hex"
"fmt"
"net"
"net/url"
"strings"
"github.com/AlexxIT/go2rtc/internal/app"
"github.com/AlexxIT/go2rtc/internal/ffmpeg"
srtp2 "github.com/AlexxIT/go2rtc/internal/srtp"
"github.com/AlexxIT/go2rtc/internal/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/hap"
"github.com/AlexxIT/go2rtc/pkg/hap/camera"
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
"github.com/AlexxIT/go2rtc/pkg/homekit"
"github.com/AlexxIT/go2rtc/pkg/magic"
"github.com/AlexxIT/go2rtc/pkg/mdns"
"github.com/AlexxIT/go2rtc/pkg/srtp"
)
type server struct {
stream string // stream name from YAML
hap *hap.Server // server for HAP connection and encryption
mdns *mdns.ServiceEntry
srtp *srtp.Server
accessory *hap.Accessory // HAP accessory
pairings []string // pairings list
streams map[string]*homekit.Consumer
consumer *homekit.Consumer
}
func (s *server) UpdateStatus() {
// true status is important, or device may be offline in Apple Home
if len(s.pairings) == 0 {
s.mdns.Info[hap.TXTStatusFlags] = hap.StatusNotPaired
} else {
s.mdns.Info[hap.TXTStatusFlags] = hap.StatusPaired
}
}
func (s *server) GetAccessories(_ net.Conn) []*hap.Accessory {
return []*hap.Accessory{s.accessory}
}
func (s *server) GetCharacteristic(conn net.Conn, aid uint8, iid uint64) any {
log.Trace().Msgf("[homekit] %s: get char aid=%d iid=0x%x", conn.RemoteAddr(), aid, iid)
char := s.accessory.GetCharacterByID(iid)
if char == nil {
log.Warn().Msgf("[homekit] get unknown characteristic: %d", iid)
return nil
}
switch char.Type {
case camera.TypeSetupEndpoints:
if s.consumer == nil {
return nil
}
answer := s.consumer.GetAnswer()
v, err := tlv8.MarshalBase64(answer)
if err != nil {
return nil
}
return v
}
return char.Value
}
func (s *server) SetCharacteristic(conn net.Conn, aid uint8, iid uint64, value any) {
log.Trace().Msgf("[homekit] %s: set char aid=%d iid=0x%x value=%v", conn.RemoteAddr(), aid, iid, value)
char := s.accessory.GetCharacterByID(iid)
if char == nil {
log.Warn().Msgf("[homekit] set unknown characteristic: %d", iid)
return
}
switch char.Type {
case camera.TypeSetupEndpoints:
var offer camera.SetupEndpoints
if err := tlv8.UnmarshalBase64(value, &offer); err != nil {
return
}
s.consumer = homekit.NewConsumer(conn, srtp2.Server)
s.consumer.SetOffer(&offer)
case camera.TypeSelectedStreamConfiguration:
var conf camera.SelectedStreamConfig
if err := tlv8.UnmarshalBase64(value, &conf); err != nil {
return
}
log.Trace().Msgf("[homekit] %s stream id=%x cmd=%d", conn.RemoteAddr(), conf.Control.SessionID, conf.Control.Command)
switch conf.Control.Command {
case camera.SessionCommandEnd:
if consumer := s.streams[conf.Control.SessionID]; consumer != nil {
_ = consumer.Stop()
}
case camera.SessionCommandStart:
if s.consumer == nil {
return
}
if !s.consumer.SetConfig(&conf) {
log.Warn().Msgf("[homekit] wrong config")
return
}
if s.streams == nil {
s.streams = map[string]*homekit.Consumer{}
}
s.streams[conf.Control.SessionID] = s.consumer
stream := streams.Get(s.stream)
if err := stream.AddConsumer(s.consumer); err != nil {
return
}
go func() {
_, _ = s.consumer.WriteTo(nil)
stream.RemoveConsumer(s.consumer)
delete(s.streams, conf.Control.SessionID)
}()
}
}
}
func (s *server) GetImage(conn net.Conn, width, height int) []byte {
log.Trace().Msgf("[homekit] %s: get image width=%d height=%d", conn.RemoteAddr(), width, height)
stream := streams.Get(s.stream)
cons := magic.NewKeyframe()
if err := stream.AddConsumer(cons); err != nil {
return nil
}
once := &core.OnceBuffer{} // init and first frame
_, _ = cons.WriteTo(once)
b := once.Buffer()
stream.RemoveConsumer(cons)
switch cons.CodecName() {
case core.CodecH264, core.CodecH265:
var err error
if b, err = ffmpeg.JPEGWithScale(b, width, height); err != nil {
return nil
}
}
return b
}
func (s *server) GetPair(conn net.Conn, id string) []byte {
log.Trace().Msgf("[homekit] %s: get pair id=%s", conn.RemoteAddr(), id)
for _, pairing := range s.pairings {
if !strings.Contains(pairing, id) {
continue
}
query, err := url.ParseQuery(pairing)
if err != nil {
continue
}
if query.Get("client_id") != id {
continue
}
s := query.Get("client_public")
b, _ := hex.DecodeString(s)
return b
}
return nil
}
func (s *server) AddPair(conn net.Conn, id string, public []byte, permissions byte) {
log.Trace().Msgf("[homekit] %s: add pair id=%s public=%x perm=%d", conn.RemoteAddr(), id, public, permissions)
query := url.Values{
"client_id": []string{id},
"client_public": []string{hex.EncodeToString(public)},
"permissions": []string{string('0' + permissions)},
}
if s.GetPair(conn, id) == nil {
s.pairings = append(s.pairings, query.Encode())
s.UpdateStatus()
s.PatchConfig()
}
}
func (s *server) DelPair(conn net.Conn, id string) {
log.Trace().Msgf("[homekit] %s: del pair id=%s", conn.RemoteAddr(), id)
id = "client_id=" + id
for i, pairing := range s.pairings {
if !strings.Contains(pairing, id) {
continue
}
s.pairings = append(s.pairings[:i], s.pairings[i+1:]...)
s.UpdateStatus()
s.PatchConfig()
break
}
}
func (s *server) PatchConfig() {
if err := app.PatchConfig([]string{"homekit", s.stream, "pairings"}, s.pairings); err != nil {
log.Error().Err(err).Msgf(
"[homekit] can't save %s pairings=%v", s.stream, s.pairings,
)
}
}
func calcName(name, seed string) string {
if name != "" {
return name
}
b := sha512.Sum512([]byte(seed))
return fmt.Sprintf("go2rtc-%02X%02X", b[0], b[2])
}
func calcDeviceID(deviceID, seed string) string {
if deviceID != "" {
if len(deviceID) >= 17 {
// 1. Returd device_id as is (ex. AA:BB:CC:DD:EE:FF)
return deviceID
}
// 2. Use device_id as seed if not zero
seed = deviceID
}
b := sha512.Sum512([]byte(seed))
return fmt.Sprintf("%02X:%02X:%02X:%02X:%02X:%02X", b[32], b[34], b[36], b[38], b[40], b[42])
}
func calcDevicePrivate(private, seed string) []byte {
if private != "" {
// 1. Decode private from HEX string
if b, _ := hex.DecodeString(private); len(b) == ed25519.PrivateKeySize {
// 2. Return if OK
return b
}
// 3. Use private as seed if not zero
seed = private
}
b := sha512.Sum512([]byte(seed))
return ed25519.NewKeyFromSeed(b[:ed25519.SeedSize])
}
+47
View File
@@ -0,0 +1,47 @@
# HTTP
This source supports receiving a stream via an HTTP link.
It can determine the source format from the`Content-Type` HTTP header:
- **HTTP-JPEG** (`image/jpeg`) - camera snapshot link, can be converted by go2rtc to MJPEG stream
- **HTTP-MJPEG** (`multipart/x-mixed-replace`) - A continuous sequence of JPEG frames (with HTTP headers).
- **HLS** (`application/vnd.apple.mpegurl`) - A popular [HTTP Live Streaming](https://en.wikipedia.org/wiki/HTTP_Live_Streaming) (HLS) format, which is not designed for real-time media transmission.
> [!WARNING]
> The HLS format is not designed for real time and is supported quite poorly. It is recommended to use it via ffmpeg source with buffering enabled (disabled by default).
## TCP
Source also supports HTTP and TCP streams with autodetection for different formats:
- `adts` - Audio stream in [AAC](https://en.wikipedia.org/wiki/Advanced_Audio_Coding) codec with Audio Data Transport Stream (ADTS) headers.
- `flv` - The legacy but still used [Flash Video](https://en.wikipedia.org/wiki/Flash_Video) format.
- `h264` - AVC/H.264 bitstream.
- `hevc` - HEVC/H.265 bitstream.
- `mjpeg` - A continuous sequence of JPEG frames (without HTTP headers).
- `mpegts` - The legacy [MPEG transport stream](https://en.wikipedia.org/wiki/MPEG_transport_stream) format.
- `wav` - Audio stream in [WAV](https://en.wikipedia.org/wiki/WAV) format.
- `yuv4mpegpipe` - Raw YUV frame stream with YUV4MPEG header.
## Configuration
```yaml
streams:
# [HTTP-FLV] stream in video/x-flv format
http_flv: http://192.168.1.123:20880/api/camera/stream/780900131155/657617
# [JPEG] snapshots from Dahua camera, will be converted to MJPEG stream
dahua_snap: http://admin:password@192.168.1.123/cgi-bin/snapshot.cgi?channel=1
# [MJPEG] stream will be proxied without modification
http_mjpeg: https://mjpeg.sanford.io/count.mjpeg
# [MJPEG or H.264/H.265 bitstream or MPEG-TS]
tcp_magic: tcp://192.168.1.123:12345
# Add custom header
custom_header: "https://mjpeg.sanford.io/count.mjpeg#header=Authorization: Bearer XXX"
```
**PS.** Dahua camera has a bug: if you select MJPEG codec for RTSP second stream, snapshot won't work.
+8
View File
@@ -111,6 +111,14 @@ func handleTCP(rawURL string) (core.Producer, error) {
}
func apiStream(w http.ResponseWriter, r *http.Request) {
if api.IsReadOnly() {
switch r.Method {
case "PUT", "PATCH", "POST", "DELETE":
api.ReadOnlyError(w)
return
}
}
dst := r.URL.Query().Get("dst")
stream := streams.Get(dst)
if stream == nil {
+30
View File
@@ -0,0 +1,30 @@
package http
import (
stdhttp "net/http"
"net/http/httptest"
"testing"
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/stretchr/testify/require"
)
func TestApiStreamReadOnly(t *testing.T) {
prevReadOnly := api.ReadOnly
t.Cleanup(func() {
api.ReadOnly = prevReadOnly
})
api.ReadOnly = true
for _, method := range []string{"PUT", "PATCH", "POST", "DELETE"} {
t.Run(method, func(t *testing.T) {
req := httptest.NewRequest(method, "/api/stream?dst=test", nil)
w := httptest.NewRecorder()
apiStream(w, req)
require.Equal(t, stdhttp.StatusForbidden, w.Code)
})
}
}
+14
View File
@@ -0,0 +1,14 @@
# Hikvision ISAPI
[`new in v1.3.0`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.3.0)
This source type supports only backchannel audio for the [Hikvision ISAPI](https://tpp.hikvision.com/download/ISAPI_OTAP) protocol. So it should be used as a second source in addition to the RTSP protocol.
## Configuration
```yaml
streams:
hikvision1:
- rtsp://admin:password@192.168.1.123:554/Streaming/Channels/101
- isapi://admin:password@192.168.1.123:80/
```
+10
View File
@@ -0,0 +1,10 @@
# Ivideon
Support public cameras from the service [Ivideon](https://tv.ivideon.com/).
## Configuration
```yaml
streams:
quailcam: ivideon:100-tu5dkUPct39cTp9oNEN2B6/0
```
+15
View File
@@ -0,0 +1,15 @@
# TP-Link Kasa
[`new in v1.7.0`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.7.0)
[TP-Link Kasa](https://www.kasasmart.com/) non-standard protocol [more info](https://medium.com/@hu3vjeen/reverse-engineering-tp-link-kc100-bac4641bf1cd).
- `username` - urlsafe email, `alex@gmail.com` -> `alex%40gmail.com`
- `password` - base64password, `secret1` -> `c2VjcmV0MQ==`
```yaml
streams:
kc401: kasa://username:password@192.168.1.123:19443/https/stream/mixed
```
Tested: KD110, KC200, KC401, KC420WS, EC71.
+13
View File
@@ -0,0 +1,13 @@
package kasa
import (
"github.com/AlexxIT/go2rtc/internal/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/kasa"
)
func Init() {
streams.HandleFunc("kasa", func(source string) (core.Producer, error) {
return kasa.Dial(source)
})
}
+75 -5
View File
@@ -1,15 +1,71 @@
## Stream as ASCII to Terminal
# Motion JPEG
- This module can provide and receive streams in MJPEG format.
- This module is also responsible for receiving snapshots in JPEG format.
- This module also supports streaming to the server console (terminal) in the **animated ASCII art** format.
## MJPEG Client
**Important.** For a stream in MJPEG format, your source MUST contain the MJPEG codec. If your stream has the MJPEG codec, you can receive an **MJPEG stream** or **JPEG snapshots** via the API.
You can receive an MJPEG stream in several ways:
- some cameras support MJPEG codec inside [RTSP stream](../rtsp/README.md) (ex. second stream for Dahua cameras)
- some cameras have an HTTP link with [MJPEG stream](../http/README.md)
- some cameras have an HTTP link with snapshots - go2rtc can convert them to [MJPEG stream](../http/README.md)
- you can convert an H264/H265 stream from your camera via [FFmpeg integration](../ffmpeg/README.md)
With this example, your stream will have both H264 and MJPEG codecs:
```yaml
streams:
camera1:
- rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0
- ffmpeg:camera1#video=mjpeg
```
## MJPEG Server
### mpjpeg
Output a stream in [MJPEG](https://en.wikipedia.org/wiki/Motion_JPEG) format. In [FFmpeg](https://ffmpeg.org/), this format is called `mpjpeg` because it contains HTTP headers.
```
ffplay http://192.168.1.123:1984/api/stream.mjpeg?src=camera1
```
### jpeg
Receiving a JPEG snapshot.
```
curl http://192.168.1.123:1984/api/frame.jpeg?src=camera1
```
- You can use `width`/`w` and/or `height`/`h` parameters.
- You can use `rotate` param with `90`, `180`, `270` or `-90` values.
- You can use `hardware`/`hw` param [read more](https://github.com/AlexxIT/go2rtc/wiki/Hardware-acceleration).
- You can use `cache` param (`1m`, `10s`, etc.) to get a cached snapshot.
- The snapshot is cached only when requested with the `cache` parameter.
- A cached snapshot will be used if its time is not older than the time specified in the `cache` parameter.
- The `cache` parameter does not check the image dimensions from the cache and those specified in the query.
### ascii
Stream as ASCII to Terminal. This format is just for fun. You can boast to your friends that you can stream cameras even to the server console without a GUI.
[![](https://img.youtube.com/vi/sHj_3h_sX7M/mqdefault.jpg)](https://www.youtube.com/watch?v=sHj_3h_sX7M)
> The demo video features a combination of several settings for this format with added audio. Of course, the format doesn't support audio out of the box.
**Tips**
- this feature works only with MJPEG codec (use transcoding)
- choose a low frame rate (FPS)
- choose the width and height to fit in your terminal
- different terminals support different numbers of colours (8, 256, rgb)
- escape text param with urlencode
- you can stream any camera or file from a disc
- different terminals support different numbers of colors (8, 256, rgb)
- URL-encode the `text` parameter
- you can stream any camera or file from disk
**go2rtc.yaml** - transcoding to MJPEG, terminal size - 210x59 (16/9), fps - 10
@@ -24,7 +80,7 @@ streams:
- example: `30` (black), `37` (white), `38;5;226` (yellow)
- `back` - background color, values: empty, `8`, `256`, `rgb`, [SGR](https://en.wikipedia.org/wiki/ANSI_escape_code)
- example: `40` (black), `47` (white), `48;5;226` (yellow)
- `text` - character set, values: empty, one char, `block`, list of chars (in order of brightness)
- `text` - character set, values: empty, one character, `block`, list of chars (in order of brightness)
- example: `%20` (space), `block` (keyword for block elements), `ox` (two chars)
**Examples**
@@ -36,3 +92,17 @@ streams:
% curl "http://192.168.1.123:1984/api/stream.ascii?src=gamazda&back=8&text=%20%20"
% curl "http://192.168.1.123:1984/api/stream.ascii?src=gamazda&text=helloworld"
```
### yuv4mpegpipe
Raw [YUV](https://en.wikipedia.org/wiki/Y%E2%80%B2UV) frame stream with [YUV4MPEG](https://manned.org/yuv4mpeg) header.
```
ffplay http://192.168.1.123:1984/api/stream.y4m?src=camera1
```
## Streaming ingest
```shell
ffmpeg -re -i BigBuckBunny.mp4 -c mjpeg -f mpjpeg http://localhost:1984/api/stream.mjpeg?dst=camera1
```
@@ -6,6 +6,7 @@ import (
"net/http"
"strconv"
"strings"
"sync"
"time"
"github.com/AlexxIT/go2rtc/internal/api"
@@ -36,12 +37,44 @@ func Init() {
var log zerolog.Logger
func handlerKeyframe(w http.ResponseWriter, r *http.Request) {
stream := streams.GetOrPatch(r.URL.Query())
query := r.URL.Query()
stream, _ := streams.GetOrPatch(query)
if stream == nil {
http.Error(w, api.StreamNotFound, http.StatusNotFound)
return
}
var b []byte
if s := query.Get("cache"); s != "" {
if timeout, err := time.ParseDuration(s); err == nil {
src := query.Get("src")
cacheMu.Lock()
entry, found := cache[src]
cacheMu.Unlock()
if found && time.Since(entry.timestamp) < timeout {
writeJPEGResponse(w, entry.payload)
return
}
defer func() {
if b == nil {
return
}
entry = cacheEntry{payload: b, timestamp: time.Now()}
cacheMu.Lock()
if cache == nil {
cache = map[string]cacheEntry{src: entry}
} else {
cache[src] = entry
}
cacheMu.Unlock()
}()
}
}
cons := magic.NewKeyframe()
cons.WithRequest(r)
@@ -52,7 +85,7 @@ func handlerKeyframe(w http.ResponseWriter, r *http.Request) {
once := &core.OnceBuffer{} // init and first frame
_, _ = cons.WriteTo(once)
b := once.Buffer()
b = once.Buffer()
stream.RemoveConsumer(cons)
@@ -60,7 +93,7 @@ func handlerKeyframe(w http.ResponseWriter, r *http.Request) {
case core.CodecH264, core.CodecH265:
ts := time.Now()
var err error
if b, err = ffmpeg.JPEGWithQuery(b, r.URL.Query()); err != nil {
if b, err = ffmpeg.JPEGWithQuery(b, query); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -69,6 +102,19 @@ func handlerKeyframe(w http.ResponseWriter, r *http.Request) {
b = mjpeg.FixJPEG(b)
}
writeJPEGResponse(w, b)
}
var cache map[string]cacheEntry
var cacheMu sync.Mutex
// cacheEntry represents a cached keyframe with its timestamp
type cacheEntry struct {
payload []byte
timestamp time.Time
}
func writeJPEGResponse(w http.ResponseWriter, b []byte) {
h := w.Header()
h.Set("Content-Type", "image/jpeg")
h.Set("Content-Length", strconv.Itoa(len(b)))
@@ -145,7 +191,7 @@ func inputMjpeg(w http.ResponseWriter, r *http.Request) {
}
func handlerWS(tr *ws.Transport, _ *ws.Message) error {
stream := streams.GetOrPatch(tr.Request.URL.Query())
stream, _ := streams.GetOrPatch(tr.Request.URL.Query())
if stream == nil {
return errors.New(api.StreamNotFound)
}
+66
View File
@@ -0,0 +1,66 @@
# MP4
This module provides several features:
1. MSE stream (fMP4 over WebSocket)
2. Camera snapshots in MP4 format (single frame), can be sent to [Telegram](#snapshot-to-telegram)
3. HTTP progressive streaming (MP4 file stream) - bad format for streaming because of high start delay. This format doesn't work in all Safari browsers, but go2rtc will automatically redirect it to HLS/fMP4 in this case.
## API examples
- MP4 snapshot: `http://192.168.1.123:1984/api/frame.mp4?src=camera1` (H264, H265)
- MP4 stream: `http://192.168.1.123:1984/api/stream.mp4?src=camera1` (H264, H265, AAC)
- MP4 file: `http://192.168.1.123:1984/api/stream.mp4?src=camera1` (H264, H265*, AAC, OPUS, MP3, PCMA, PCMU, PCM)
- You can use `mp4`, `mp4=flac` and `mp4=all` param for codec filters
- You can use `duration` param in seconds (ex. `duration=15`)
- You can use `filename` param (ex. `filename=record.mp4`)
- You can use `rotate` param with `90`, `180` or `270` values
- You can use `scale` param with positive integer values (ex. `scale=4:3`)
Read more about [codecs filters](../../README.md#codecs-filters).
**PS.** Rotate and scale params don't use transcoding and change video using metadata.
## Snapshot to Telegram
This examples for Home Assistant [Telegram Bot](https://www.home-assistant.io/integrations/telegram_bot/) integration.
- change `url` to your go2rtc web API (`http://localhost:1984/` for most users)
- change `target` to your Telegram chat ID (support list)
- change `src=camera1` to your stream name from go2rtc config
**Important.** Snapshot will be near instant for most cameras and many sources, except `ffmpeg` source. Because it takes a long time for ffmpeg to start streaming with video, even when you use `#video=copy`. Also the delay can be with cameras that do not start the stream with a keyframe.
### Snapshot from H264 or H265 camera
```yaml
service: telegram_bot.send_video
data:
url: http://localhost:1984/api/frame.mp4?src=camera1
target: 123456789
```
### Record from H264 or H265 camera
Record from service call to the future. Doesn't support loopback.
- `mp4=flac` - adds support PCM audio family
- `filename=record.mp4` - set name for downloaded file
```yaml
service: telegram_bot.send_video
data:
url: http://localhost:1984/api/stream.mp4?src=camera1&mp4=flac&duration=5&filename=record.mp4 # duration in seconds
target: 123456789
```
### Snapshot from JPEG or MJPEG camera
This example works via the [mjpeg](../mjpeg/README.md) module.
```yaml
service: telegram_bot.send_photo
data:
url: http://localhost:1984/api/frame.jpeg?src=camera1
target: 123456789
```
+1 -1
View File
@@ -91,7 +91,7 @@ func handlerMP4(w http.ResponseWriter, r *http.Request) {
return
}
stream := streams.GetOrPatch(query)
stream, _ := streams.GetOrPatch(query)
if stream == nil {
http.Error(w, api.StreamNotFound, http.StatusNotFound)
return
+2 -2
View File
@@ -11,7 +11,7 @@ import (
)
func handlerWSMSE(tr *ws.Transport, msg *ws.Message) error {
stream := streams.GetOrPatch(tr.Request.URL.Query())
stream, _ := streams.GetOrPatch(tr.Request.URL.Query())
if stream == nil {
return errors.New(api.StreamNotFound)
}
@@ -43,7 +43,7 @@ func handlerWSMSE(tr *ws.Transport, msg *ws.Message) error {
}
func handlerWSMP4(tr *ws.Transport, msg *ws.Message) error {
stream := streams.GetOrPatch(tr.Request.URL.Query())
stream, _ := streams.GetOrPatch(tr.Request.URL.Query())
if stream == nil {
return errors.New(api.StreamNotFound)
}
+25
View File
@@ -0,0 +1,25 @@
# MPEG
This module provides an [HTTP API](../api/README.md) for:
- Streaming output in `mpegts` format.
- Streaming output in `adts` format.
- Streaming ingest in `mpegts` format.
## MPEG-TS Server
```shell
ffplay http://localhost:1984/api/stream.ts?src=camera1
```
## ADTS Server
```shell
ffplay http://localhost:1984/api/stream.aac?src=camera1
```
## Streaming ingest
```shell
ffmpeg -re -i BigBuckBunny.mp4 -c copy -f mpegts http://localhost:1984/api/stream.ts?dst=camera1
```
@@ -1,10 +1,11 @@
package mpegts
package mpeg
import (
"net/http"
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/internal/streams"
"github.com/AlexxIT/go2rtc/pkg/aac"
"github.com/AlexxIT/go2rtc/pkg/mpegts"
)
@@ -66,3 +67,26 @@ func inputMpegTS(w http.ResponseWriter, r *http.Request) {
return
}
}
func apiStreamAAC(w http.ResponseWriter, r *http.Request) {
src := r.URL.Query().Get("src")
stream := streams.Get(src)
if stream == nil {
http.Error(w, api.StreamNotFound, http.StatusNotFound)
return
}
cons := aac.NewConsumer()
cons.WithRequest(r)
if err := stream.AddConsumer(cons); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Add("Content-Type", "audio/aac")
_, _ = cons.WriteTo(w)
stream.RemoveConsumer(cons)
}
-32
View File
@@ -1,32 +0,0 @@
package mpegts
import (
"net/http"
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/internal/streams"
"github.com/AlexxIT/go2rtc/pkg/aac"
)
func apiStreamAAC(w http.ResponseWriter, r *http.Request) {
src := r.URL.Query().Get("src")
stream := streams.Get(src)
if stream == nil {
http.Error(w, api.StreamNotFound, http.StatusNotFound)
return
}
cons := aac.NewConsumer()
cons.WithRequest(r)
if err := stream.AddConsumer(cons); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Add("Content-Type", "audio/aac")
_, _ = cons.WriteTo(w)
stream.RemoveConsumer(cons)
}
+22
View File
@@ -0,0 +1,22 @@
# TP-Link MULTITRANS
[`new in v1.9.14`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.9.14) by [@forrestsocool](https://github.com/forrestsocool)
Two-way audio support for Chinese version of [TP-Link](https://www.tp-link.com.cn/) cameras.
## Configuration
```yaml
streams:
tplink_cam:
# video uses standard RTSP
- rtsp://admin:admin@192.168.1.202:554/stream1
# two-way audio uses MULTITRANS schema
- multitrans://admin:admin@192.168.1.202:554
```
## Useful links
- https://www.tp-link.com.cn/list_2549.html
- https://github.com/AlexxIT/go2rtc/issues/1724
- https://github.com/bingooo/hass-tplink-ipc/
+10
View File
@@ -0,0 +1,10 @@
package multitrans
import (
"github.com/AlexxIT/go2rtc/internal/streams"
"github.com/AlexxIT/go2rtc/pkg/multitrans"
)
func Init() {
streams.HandleFunc("multitrans", multitrans.Dial)
}
+11
View File
@@ -0,0 +1,11 @@
# Google Nest
[`new in v1.6.0`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.6.0)
For simplicity, it is recommended to connect the Nest/WebRTC camera to the [Home Assistant](../hass/README.md).
But if you can somehow get the below parameters, Nest/WebRTC source will work without Home Assistant.
```yaml
streams:
nest-doorbell: nest:?client_id=***&client_secret=***&refresh_token=***&project_id=***&device_id=***
```
+54
View File
@@ -0,0 +1,54 @@
# ngrok
With the ngrok integration, you can get external access to your streams when your Internet connection is behind a private IP address.
- you may need external access for two different things:
- WebRTC streams (tunnel the WebRTC TCP port, e.g. 8555)
- go2rtc web interface (tunnel the API HTTP port, e.g. 1984)
- ngrok supports authorization for your web interface
- ngrok automatically adds HTTPS to your web interface
The ngrok free subscription has the following limitations:
- You can reserve a free domain for serving the web interface, but the TCP address you get will always be random and will change with each restart of the ngrok agent (not a problem for WebRTC streams)
- You can forward multiple ports from a single agent, but you can only run one ngrok agent on the free plan
go2rtc will automatically get your external TCP address (if you enable it in the ngrok config) and use it for WebRTC connections (if you enable it in the WebRTC config).
You need to manually download the [ngrok agent](https://ngrok.com/download) for your OS and register with the [ngrok service](https://ngrok.com/signup).
**Tunnel for only WebRTC Stream**
You need to add your [ngrok authtoken](https://dashboard.ngrok.com/get-started/your-authtoken) and WebRTC TCP port to YAML:
```yaml
ngrok:
command: ngrok tcp 8555 --authtoken eW91IHNoYWxsIG5vdCBwYXNzCnlvdSBzaGFsbCBub3QgcGFzcw
```
**Tunnel for WebRTC and Web interface**
You need to create `ngrok.yaml` config file and add it to the go2rtc config:
```yaml
ngrok:
command: ngrok start --all --config ngrok.yaml
```
ngrok config example:
```yaml
version: "2"
authtoken: eW91IHNoYWxsIG5vdCBwYXNzCnlvdSBzaGFsbCBub3QgcGFzcw
tunnels:
api:
addr: 1984 # use the same port as in the go2rtc config
proto: http
basic_auth:
- admin:password # you can set login/pass for your web interface
webrtc:
addr: 8555 # use the same port as in the go2rtc config
proto: tcp
```
See the [ngrok agent documentation](https://ngrok.com/docs/agent/config/) for more details on the ngrok configuration file.
+18 -1
View File
@@ -1,5 +1,22 @@
# ONVIF
## ONVIF Client
[`new in v1.5.0`](https://github.com/AlexxIT/go2rtc/releases/tag/v1.5.0)
The source is not very useful if you already know RTSP and snapshot links for your camera. But it can be useful if you don't.
**WebUI > Add** webpage supports ONVIF autodiscovery. Your server must be on the same subnet as the camera. If you use Docker, you must use "network host".
```yaml
streams:
dahua1: onvif://admin:password@192.168.1.123
reolink1: onvif://admin:password@192.168.1.123:8000
tapo1: onvif://admin:password@192.168.1.123:2020
```
## ONVIF Server
A regular camera has a single video source (`GetVideoSources`) and two profiles (`GetProfiles`).
Go2rtc has one video source and one profile per stream.
@@ -13,7 +30,7 @@ Go2rtc works as ONVIF server:
- Onvier (android)
- ONVIF Device Manager (windows)
PS. Support only TCP transport for RTSP protocol. UDP and HTTP transports - unsupported yet.
PS. Supports only TCP transport for RTSP protocol. UDP and HTTP transports - unsupported yet.
## Tested cameras

Some files were not shown because too many files have changed in this diff Show More