Repository: bluenviron/mediamtx

Issue:
Unable to use evniroment to set internal authentication user and password
### Which version are you using?

v1.15.3

### Which operating system are you using?

Linux amd64 Docker

### Describe the issue

Hello,

I'm trying to setup a public test RTSP server which I can use as a RTSP source.
I've managed to use FFmpeg to push video to the server and now I want to make it secure and prevent others from pushing video to the server while making anyone able to read from it.
I've decided to use an env variable to set user and password to hopefully avoid commiting them, but while in `runOnInit` the parameters get replaced correctly, the `user` and `pass` fields won't. If I replace `${RTSP_STREAMER_USER}` and `${RTSP_STREAMER_PASS}` (on in the auth config) with the actual values it will work, so it's not an issue of the environment variables getting read.

Any help? Thx

Rico

### Describe how to replicate the issue

1. Set config as provided
2. Set `RTSP_STREAMER_USER` and `RTSP_STREAMER_PASS` environment variables (I'm using a `.env` file and importing it with docker compose)
3. Provide any random video file to test and edit the config accordingly
4. Start mediamtx
5. Check logs for errors

### MediaMTX configuration
```yml
# mediamtx.yml
logLevel: info

api: true

authMethod: internal
authInternalUsers:
  - user: ${RTSP_STREAMER_USER}
    pass: ${RTSP_STREAMER_PASS}
    ips: []
    permissions:
    - action: publish
      path:
    - action: read
      path:
    - action: playback
      path:
  - user: any
    pass:
    ips: []
    permissions:
    - action: read
      path:
    - action: playback
      path:

paths:
  big-buck-bunny:
    source: publisher
    runOnInit: >
      ffmpeg -re -stream_loop -1 -i /videos/bbb.mp4
      -c copy -f rtsp -rtsp_transport tcp rtsp://${RTSP_STREAMER_USER}:${RTSP_STREAMER_PASS}@localhost:8554/big-buck-bunny
```

### MediaMTX logs

```
[rtsp @ 0x7f8c95d17640] method ANNOUNCE failed: 401 Unauthorized
[out#0/rtsp @ 0x7f8c94f23680] Could not write header (incorrect codec parameters ?): Server returned 401 Unauthorized (authorization failed)
Error opening output file rtsp://streamer:fVMA4D7Y@localhost:8554/big-buck-bunny.
Error opening output files: Server returned 401 Unauthorized (authorization failed)
2025/10/27 08:09:13 INF [RTSP] [conn [::1]:45332] closed: authentication failed: authentication failed
```

### Network dump

_No response_

Discussion:
@malmgrens4: See here: 
https://github.com/bluenviron/mediamtx/discussions/3378

Solution patch:
diff --git a/.github/workflows/code_lint.yml b/.github/workflows/code_lint.yml
index 41149705..000642a9 100644
--- a/.github/workflows/code_lint.yml
+++ b/.github/workflows/code_lint.yml
@@ -21,7 +21,7 @@ jobs:
 
     - run: go generate ./...
 
-    - uses: golangci/golangci-lint-action@v8
+    - uses: golangci/golangci-lint-action@v9
       with:
         version: v2.6.1
 
diff --git a/.golangci.yml b/.golangci.yml
index 77c31197..a3dfcbca 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -12,6 +12,7 @@ linters:
   - gocritic
   - lll
   - misspell
+  - modernize
   - nilerr
   - prealloc
   - predeclared
@@ -71,6 +72,12 @@ linters:
         shadow:
           strict: true
 
+    modernize:
+      disable:
+      - reflecttypefor
+      - stringsbuilder
+      - testingcontext
+
 formatters:
   enable:
   - gofmt
diff --git a/docs/2-usage/04-configuration.md b/docs/2-usage/04-configuration.md
index 140e3649..f26c628d 100644
--- a/docs/2-usage/04-configuration.md
+++ b/docs/2-usage/04-configuration.md
@@ -32,6 +32,13 @@ There are several ways to change the configuration:
    MTX_PATHS_TEST_SOURCE=rtsp://myurl ./mediamtx
    ```
 
+   Parameters in lists can be overridden in the same way as parameters in maps, using their position like an additional key. This is particularly useful if you want to use internal users but define credentials through enviroment variables:
+
+   ```
+   MTX_AUTHINTERNALUSERS_0_USER=username
+   MTX_AUTHINTERNALUSERS_0_PASS=password
+   ```
+
    This method is particularly useful when using Docker; any configuration parameter can be changed by passing environment variables with the `-e` flag:
 
    ```
diff --git a/go.mod b/go.mod
index 6ec1462b..d5db29ec 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/bluenviron/mediamtx
 go 1.25.0
 
 require (
-	code.cloudfoundry.org/bytefmt v0.56.0
+	code.cloudfoundry.org/bytefmt v0.57.0
 	github.com/Masterminds/semver/v3 v3.4.0
 	github.com/MicahParks/jwkset v0.11.0
 	github.com/MicahParks/keyfunc/v3 v3.7.0
@@ -12,7 +12,7 @@ require (
 	github.com/asticode/go-astits v1.14.0
 	github.com/bluenviron/gohlslib/v2 v2.2.3
 	github.com/bluenviron/gortmplib v0.1.1
-	github.com/bluenviron/gortsplib/v5 v5.1.2-0.20251026101219-59474a8fa3ab
+	github.com/bluenviron/gortsplib/v5 v5.1.2-0.20251112170709-0e56f305d197
 	github.com/bluenviron/mediacommon/v2 v2.5.1
 	github.com/datarhei/gosrt v0.9.0
 	github.com/fsnotify/fsnotify v1.9.0
@@ -25,7 +25,7 @@ require (
 	github.com/gookit/color v1.6.0
 	github.com/gorilla/websocket v1.5.3
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
-	github.com/matthewhartstonge/argon2 v1.4.1
+	github.com/matthewhartstonge/argon2 v1.4.2
 	github.com/minio/selfupdate v0.6.0
 	github.com/pion/ice/v4 v4.0.10
 	github.com/pion/interceptor v0.1.41
@@ -33,12 +33,12 @@ require (
 	github.com/pion/rtcp v1.2.16
 	github.com/pion/rtp v1.8.23
 	github.com/pion/sdp/v3 v3.0.16
-	github.com/pion/transport/v3 v3.0.8
+	github.com/pion/transport/v3 v3.1.1
 	github.com/pion/webrtc/v4 v4.1.6
 	github.com/stretchr/testify v1.11.1
-	golang.org/x/crypto v0.43.0
-	golang.org/x/sys v0.37.0
-	golang.org/x/term v0.36.0
+	golang.org/x/crypto v0.44.0
+	golang.org/x/sys v0.38.0
+	golang.org/x/term v0.37.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
@@ -96,9 +96,9 @@ require (
 	go.uber.org/mock v0.5.0 // indirect
 	golang.org/x/arch v0.20.0 // indirect
 	golang.org/x/mod v0.29.0 // indirect
-	golang.org/x/net v0.46.0 // indirect
-	golang.org/x/sync v0.17.0 // indirect
-	golang.org/x/text v0.30.0 // indirect
+	golang.org/x/net v0.47.0 // indirect
+	golang.org/x/sync v0.18.0 // indirect
+	golang.org/x/text v0.31.0 // indirect
 	golang.org/x/time v0.9.0 // indirect
 	golang.org/x/tools v0.38.0 // indirect
 	google.golang.org/protobuf v1.36.9 // indirect
diff --git a/go.sum b/go.sum
index 050511c3..710d1efe 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
 aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk=
 aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
-code.cloudfoundry.org/bytefmt v0.56.0 h1:qsCKmisDuotACI4KYPNdORvN6WaWEEb6RX2d9gyysNw=
-code.cloudfoundry.org/bytefmt v0.56.0/go.mod h1:J23lJHgcn710FEpbcOw5cqOUsj7K0Z2O1npk8m0adhU=
+code.cloudfoundry.org/bytefmt v0.57.0 h1:aIEsL1Gm1uZIX//g2i0r1f9PVLYCpBPFn/dHEyxLQGI=
+code.cloudfoundry.org/bytefmt v0.57.0/go.mod h1:z2PooTd9shTfkDak4rOuhTQ2I1C+0UMXHd5L3bM3vyo=
 dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
 dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
 github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
@@ -37,8 +37,8 @@ github.com/bluenviron/gohlslib/v2 v2.2.3 h1:1R/Jnh1kNR9UB09KAX6xjS2GcdKFRLuPd9wM
 github.com/bluenviron/gohlslib/v2 v2.2.3/go.mod h1:z4Viks+Mdgcl7OcOVJ1fgSmuUwCCJBxYJPLN49n7Vnw=
 github.com/bluenviron/gortmplib v0.1.1 h1:pmR6qfPcJJmE17lWQ/bpuBFZtgGnMrN8KdFj1Gl/ZoQ=
 github.com/bluenviron/gortmplib v0.1.1/go.mod h1:XWy2YzbTP1XEEZ8232OG7I1MSwubsbDRKDNhXGgS2kg=
-github.com/bluenviron/gortsplib/v5 v5.1.2-0.20251026101219-59474a8fa3ab h1:9QH6j4y2FMo299Rz/hX5jrhI+THhWgZ/oSiEmwSPavk=
-github.com/bluenviron/gortsplib/v5 v5.1.2-0.20251026101219-59474a8fa3ab/go.mod h1:+4E4JNF7dpDu8LgssZu9fB3Ndh6FNbvGYMKOKR/wvvI=
+github.com/bluenviron/gortsplib/v5 v5.1.2-0.20251112170709-0e56f305d197 h1:NvBCxleaHdQwxZLpck4JwsOFYupbtg9+QnWnvRD2CYc=
+github.com/bluenviron/gortsplib/v5 v5.1.2-0.20251112170709-0e56f305d197/go.mod h1:IPHhCqTQvmKZ9t101dfnsf8xvVHM2oGGmxxEH8KMof4=
 github.com/bluenviron/mediacommon/v2 v2.5.1 h1:qB2fb5c0xyl5OB2gfSfulpEJn7Cdm3vI2n8wjiLMxKI=
 github.com/bluenviron/mediacommon/v2 v2.5.1/go.mod h1:zy1fODPuS/kBd93ftgJS1Jhvjq7LFWfAo32KP7By9AE=
 github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
@@ -137,8 +137,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
 github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
-github.com/matthewhartstonge/argon2 v1.4.1 h1:FNqWx6rMzsWMELIP5bBjTQ9SwBrJLhfxLEKDva8ZlIE=
-github.com/matthewhartstonge/argon2 v1.4.1/go.mod h1:o7LXmwzMcaYgydER/0TBK95M2F4kRqcAhpX+7pnW3aA=
+github.com/matthewhartstonge/argon2 v1.4.2 h1:ucPhURgXjjo85D5N1bINBligyWZrWkxCgwbFfsilJaI=
+github.com/matthewhartstonge/argon2 v1.4.2/go.mod h1:mmptUzJqB46iKs9sZDgy/tRKA2yIZM8g9WYWG3vfLdI=
 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/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
@@ -183,8 +183,8 @@ github.com/pion/srtp/v3 v3.0.8 h1:RjRrjcIeQsilPzxvdaElN0CpuQZdMvcl9VZ5UY9suUM=
 github.com/pion/srtp/v3 v3.0.8/go.mod h1:2Sq6YnDH7/UDCvkSoHSDNDeyBcFgWL0sAVycVbAsXFg=
 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.8 h1:oI3myyYnTKUSTthu/NZZ8eu2I5sHbxbUNNFW62olaYc=
-github.com/pion/transport/v3 v3.0.8/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
+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/turn/v4 v4.1.1 h1:9UnY2HB99tpDyz3cVVZguSxcqkJ1DsTSZ+8TGruh4fc=
 github.com/pion/turn/v4 v4.1.1/go.mod h1:2123tHk1O++vmjI5VSD0awT50NywDAq5A2NNNU4Jjs8=
 github.com/pion/webrtc/v4 v4.1.6 h1:srHH2HwvCGwPba25EYJgUzgLqCQoXl1VCUnrGQMSzUw=
@@ -239,18 +239,18 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
-golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
+golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
+golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
 golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
 golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
-golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
-golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
-golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
+golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
+golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -261,17 +261,17 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
-golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 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.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
-golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
+golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
+golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
-golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
 golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
 golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/internal/api/api.go b/internal/api/api.go
index 8a628fde..bd6d33aa 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -30,7 +30,7 @@ import (
 	"github.com/bluenviron/mediamtx/internal/servers/webrtc"
 )
 
-func interfaceIsEmpty(i interface{}) bool {
+func interfaceIsEmpty(i any) bool {
 	return reflect.ValueOf(i).Kind() != reflect.Ptr || reflect.ValueOf(i).IsNil()
 }
 
@@ -220,7 +220,7 @@ func (a *API) Close() {
 }
 
 // Log implements logger.Writer.
-func (a *API) Log(level logger.Level, format string, args ...interface{}) {
+func (a *API) Log(level logger.Level, format string, args ...any) {
 	a.Parent.Log(level, "[API] "+format, args...)
 }
 
diff --git a/internal/api/paginate.go b/internal/api/paginate.go
index 6d58f1ee..c8ab3733 100644
--- a/internal/api/paginate.go
+++ b/internal/api/paginate.go
@@ -6,7 +6,7 @@ import (
 	"strconv"
 )
 
-func paginate2(itemsPtr interface{}, itemsPerPage int, page int) int {
+func paginate2(itemsPtr any, itemsPerPage int, page int) int {
 	ritems := reflect.ValueOf(itemsPtr).Elem()
 
 	itemsLen := ritems.Len()
@@ -19,22 +19,16 @@ func paginate2(itemsPtr interface{}, itemsPerPage int, page int) int {
 		pageCount++
 	}
 
-	minVal := page * itemsPerPage
-	if minVal > itemsLen {
-		minVal = itemsLen
-	}
+	minVal := min(page*itemsPerPage, itemsLen)
 
-	maxVal := (page + 1) * itemsPerPage
-	if maxVal > itemsLen {
-		maxVal = itemsLen
-	}
+	maxVal := min((page+1)*itemsPerPage, itemsLen)
 
 	ritems.Set(ritems.Slice(minVal, maxVal))
 
 	return pageCount
 }
 
-func paginate(itemsPtr interface{}, itemsPerPageStr string, pageStr string) (int, error) {
+func paginate(itemsPtr any, itemsPerPageStr string, pageStr string) (int, error) {
 	itemsPerPage := 100
 
 	if itemsPerPageStr != "" {
diff --git a/internal/conf/conf.go b/internal/conf/conf.go
index baa1b02f..8595adda 100644
--- a/internal/conf/conf.go
+++ b/internal/conf/conf.go
@@ -48,13 +48,13 @@ func firstThatExists(paths []string) string {
 	return ""
 }
 
-func copyStructFields(dest interface{}, source interface{}) {
+func copyStructFields(dest any, source any) {
 	rvsource := reflect.ValueOf(source).Elem()
 	rvdest := reflect.ValueOf(dest)
 	nf := rvsource.NumField()
 	var zero reflect.Value
 
-	for i := 0; i < nf; i++ {
+	for i := range nf {
 		fnew := rvsource.Field(i)
 		f := rvdest.Elem().FieldByName(rvsource.Type().Field(i).Name)
 		if f == zero {
@@ -508,7 +508,7 @@ func (conf Conf) Clone() *Conf {
 
 type nilLogger struct{}
 
-func (nilLogger) Log(_ logger.Level, _ string, _ ...interface{}) {
+func (nilLogger) Log(_ logger.Level, _ string, _ ...any) {
 }
 
 // Validate checks the configuration for errors.
diff --git a/internal/conf/env/env.go b/internal/conf/env/env.go
index 64035742..4e2653b5 100644
--- a/internal/conf/env/env.go
+++ b/internal/conf/env/env.go
@@ -156,7 +156,7 @@ func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) er
 
 	case reflect.Struct:
 		flen := rt.NumField()
-		for i := 0; i < flen; i++ {
+		for i := range flen {
 			f := rt.Field(i)
 			jsonTag := f.Tag.Get("json")
 
@@ -239,7 +239,7 @@ func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) er
 	return fmt.Errorf("unsupported type: %v", rt)
 }
 
-func loadWithEnv(env map[string]string, prefix string, v interface{}) error {
+func loadWithEnv(env map[string]string, prefix string, v any) error {
 	return loadEnvInternal(env, prefix, reflect.ValueOf(v).Elem())
 }
 
@@ -253,6 +253,6 @@ func envToMap() map[string]string {
 }
 
 // Load loads the configuration from the environment.
-func Load(prefix string, v interface{}) error {
+func Load(prefix string, v any) error {
 	return loadWithEnv(envToMap(), prefix, v)
 }
diff --git a/internal/conf/global.go b/internal/conf/global.go
index 51ee02a4..a5e15652 100644
--- a/internal/conf/global.go
+++ b/internal/conf/global.go
@@ -10,7 +10,7 @@ var globalValuesType = func() reflect.Type {
 	rt := reflect.TypeOf(Conf{})
 	nf := rt.NumField()
 
-	for i := 0; i < nf; i++ {
+	for i := range nf {
 		f := rt.Field(i)
 		j := f.Tag.Get("json")
 
@@ -26,13 +26,13 @@ var globalValuesType = func() reflect.Type {
 	return reflect.StructOf(fields)
 }()
 
-func newGlobalValues() interface{} {
+func newGlobalValues() any {
 	return reflect.New(globalValuesType).Interface()
 }
 
 // Global is the global part of Conf.
 type Global struct {
-	Values interface{}
+	Values any
 }
 
 // MarshalJSON implements json.Marshaler.
diff --git a/internal/conf/log_destination.go b/internal/conf/log_destination.go
index a1303f5a..27695676 100644
--- a/internal/conf/log_destination.go
+++ b/internal/conf/log_destination.go
@@ -3,6 +3,7 @@ package conf
 import (
 	"encoding/json"
 	"fmt"
+	"slices"
 	"strings"
 
 	"github.com/bluenviron/mediamtx/internal/conf/jsonwrapper"
@@ -39,12 +40,7 @@ func (d LogDestinations) MarshalJSON() ([]byte, error) {
 }
 
 func (d *LogDestinations) contains(v logger.Destination) bool {
-	for _, item := range *d {
-		if item == v {
-			return true
-		}
-	}
-	return false
+	return slices.Contains(*d, v)
 }
 
 // UnmarshalJSON implements json.Unmarshaler.
diff --git a/internal/conf/optional_global.go b/internal/conf/optional_global.go
index c265622c..5b26f549 100644
--- a/internal/conf/optional_global.go
+++ b/internal/conf/optional_global.go
@@ -13,7 +13,7 @@ var optionalGlobalValuesType = func() reflect.Type {
 	rt := reflect.TypeOf(Conf{})
 	nf := rt.NumField()
 
-	for i := 0; i < nf; i++ {
+	for i := range nf {
 		f := rt.Field(i)
 		j := f.Tag.Get("json")
 
@@ -38,13 +38,13 @@ var optionalGlobalValuesType = func() reflect.Type {
 	return reflect.StructOf(fields)
 }()
 
-func newOptionalGlobalValues() interface{} {
+func newOptionalGlobalValues() any {
 	return reflect.New(optionalGlobalValuesType).Interface()
 }
 
 // OptionalGlobal is a Conf whose values can all be optional.
 type OptionalGlobal struct {
-	Values interface{}
+	Values any
 }
 
 // UnmarshalJSON implements json.Unmarshaler.
diff --git a/internal/conf/optional_path.go b/internal/conf/optional_path.go
index 3039c3b8..18386ae5 100644
--- a/internal/conf/optional_path.go
+++ b/internal/conf/optional_path.go
@@ -14,7 +14,7 @@ var optionalPathValuesType = func() reflect.Type {
 	rt := reflect.TypeOf(Path{})
 	nf := rt.NumField()
 
-	for i := 0; i < nf; i++ {
+	for i := range nf {
 		f := rt.Field(i)
 		j := f.Tag.Get("json")
 
@@ -39,13 +39,13 @@ var optionalPathValuesType = func() reflect.Type {
 	return reflect.StructOf(fields)
 }()
 
-func newOptionalPathValues() interface{} {
+func newOptionalPathValues() any {
 	return reflect.New(optionalPathValuesType).Interface()
 }
 
 // OptionalPath is a Path whose values can all be optional.
 type OptionalPath struct {
-	Values interface{}
+	Values any
 }
 
 // UnmarshalJSON implements json.Unmarshaler.
diff --git a/internal/conf/rtsp_transport.go b/internal/conf/rtsp_transport.go
index bda08c36..65c368ec 100644
--- a/internal/conf/rtsp_transport.go
+++ b/internal/conf/rtsp_transport.go
@@ -8,6 +8,10 @@ import (
 	"github.com/bluenviron/mediamtx/internal/conf/jsonwrapper"
 )
 
+func ptrOf[T any](v T) *T {
+	return &v
+}
+
 // RTSPTransport is the rtspTransport parameter.
 type RTSPTransport struct {
 	*gortsplib.Protocol
@@ -44,16 +48,13 @@ func (d *RTSPTransport) UnmarshalJSON(b []byte) error {
 
 	switch in {
 	case "udp":
-		v := gortsplib.ProtocolUDP
-		d.Protocol = &v
+		d.Protocol = ptrOf(gortsplib.ProtocolUDP)
 
 	case "multicast":
-		v := gortsplib.ProtocolUDPMulticast
-		d.Protocol = &v
+		d.Protocol = ptrOf(gortsplib.ProtocolUDPMulticast)
 
 	case "tcp":
-		v := gortsplib.ProtocolTCP
-		d.Protocol = &v
+		d.Protocol = ptrOf(gortsplib.ProtocolTCP)
 
 	case "automatic":
 		d.Protocol = nil
diff --git a/internal/conf/yamlwrapper/unmarshal.go b/internal/conf/yamlwrapper/unmarshal.go
index 4dfed80e..69c15fdb 100644
--- a/internal/conf/yamlwrapper/unmarshal.go
+++ b/internal/conf/yamlwrapper/unmarshal.go
@@ -8,10 +8,10 @@ import (
 	"gopkg.in/yaml.v2"
 )
 
-func convertKeys(i interface{}) (interface{}, error) {
+func convertKeys(i any) (any, error) {
 	switch x := i.(type) {
-	case map[interface{}]interface{}:
-		m2 := map[string]interface{}{}
+	case map[any]any:
+		m2 := map[string]any{}
 		for k, v := range x {
 			ks, ok := k.(string)
 			if !ok {
@@ -26,8 +26,8 @@ func convertKeys(i interface{}) (interface{}, error) {
 		}
 		return m2, nil
 
-	case []interface{}:
-		a2 := make([]interface{}, len(x))
+	case []any:
+		a2 := make([]any, len(x))
 		for i, v := range x {
 			var err error
 			a2[i], err = convertKeys(v)
@@ -42,12 +42,12 @@ func convertKeys(i interface{}) (interface{}, error) {
 }
 
 // Unmarshal loads the configuration from YAML.
-func Unmarshal(buf []byte, dest interface{}) error {
+func Unmarshal(buf []byte, dest any) error {
 	// load YAML into a generic map
 	// from documentation:
 	// "UnmarshalStrict is like Unmarshal except that any fields that are found in the data
 	// that do not have corresponding struct members, or mapping keys that are duplicates, will result in an error."
-	var temp interface{}
+	var temp any
 	err := yaml.UnmarshalStrict(buf, &temp)
 	if err != nil {
 		return err
diff --git a/internal/core/core.go b/internal/core/core.go
index 6856f3e5..b6907dc0 100644
--- a/internal/core/core.go
+++ b/internal/core/core.go
@@ -197,7 +197,7 @@ func (p *Core) Wait() {
 }
 
 // Log implements logger.Writer.
-func (p *Core) Log(level logger.Level, format string, args ...interface{}) {
+func (p *Core) Log(level logger.Level, format string, args ...any) {
 	p.logger.Log(level, format, args...)
 }
 
diff --git a/internal/core/path.go b/internal/core/path.go
index bc07f04a..92b32109 100644
--- a/internal/core/path.go
+++ b/internal/core/path.go
@@ -150,7 +150,7 @@ func (pa *path) wait() {
 }
 
 // Log implements logger.Writer.
-func (pa *path) Log(level logger.Level, format string, args ...interface{}) {
+func (pa *path) Log(level logger.Level, format string, args ...any) {
 	pa.parent.Log(level, "[path "+pa.name+"] "+format, args...)
 }
 
diff --git a/internal/core/path_manager.go b/internal/core/path_manager.go
index 2d246ccd..5f928ff4 100644
--- a/internal/core/path_manager.go
+++ b/internal/core/path_manager.go
@@ -149,7 +149,7 @@ func (pm *pathManager) close() {
 }
 
 // Log implements logger.Writer.
-func (pm *pathManager) Log(level logger.Level, format string, args ...interface{}) {
+func (pm *pathManager) Log(level logger.Level, format string, args ...any) {
 	pm.parent.Log(level, format, args...)
 }
 
diff --git a/internal/core/source_redirect.go b/internal/core/source_redirect.go
index 9428430d..99744415 100644
--- a/internal/core/source_redirect.go
+++ b/internal/core/source_redirect.go
@@ -8,7 +8,7 @@ import (
 // sourceRedirect is a source that redirects to another one.
 type sourceRedirect struct{}
 
-func (*sourceRedirect) Log(logger.Level, string, ...interface{}) {
+func (*sourceRedirect) Log(logger.Level, string, ...any) {
 }
 
 // APISourceDescribe implements source.
diff --git a/internal/logger/destination.go b/internal/logger/destination.go
index b4874292..75be4b1d 100644
--- a/internal/logger/destination.go
+++ b/internal/logger/destination.go
@@ -19,6 +19,6 @@ const (
 )
 
 type destination interface {
-	log(time.Time, Level, string, ...interface{})
+	log(time.Time, Level, string, ...any)
 	close()
 }
diff --git a/internal/logger/destination_file.go b/internal/logger/destination_file.go
index 46ba8c38..08e8a89a 100644
--- a/internal/logger/destination_file.go
+++ b/internal/logger/destination_file.go
@@ -22,7 +22,7 @@ func newDestinationFile(filePath string) (destination, error) {
 	}, nil
 }
 
-func (d *destinationFile) log(t time.Time, level Level, format string, args ...interface{}) {
+func (d *destinationFile) log(t time.Time, level Level, format string, args ...any) {
 	d.buf.Reset()
 	writeTime(&d.buf, t, false)
 	writeLevel(&d.buf, level, false)
diff --git a/internal/logger/destination_stdout.go b/internal/logger/destination_stdout.go
index 4df94c2b..43c11bed 100644
--- a/internal/logger/destination_stdout.go
+++ b/internal/logger/destination_stdout.go
@@ -20,7 +20,7 @@ func newDestionationStdout() destination {
 	}
 }
 
-func (d *destinationStdout) log(t time.Time, level Level, format string, args ...interface{}) {
+func (d *destinationStdout) log(t time.Time, level Level, format string, args ...any) {
 	d.buf.Reset()
 	writeTime(&d.buf, t, d.useColor)
 	writeLevel(&d.buf, level, d.useColor)
diff --git a/internal/logger/destination_syslog.go b/internal/logger/destination_syslog.go
index eacb2fb7..e9a8d4ed 100644
--- a/internal/logger/destination_syslog.go
+++ b/internal/logger/destination_syslog.go
@@ -22,7 +22,7 @@ func newDestinationSyslog(prefix string) (destination, error) {
 	}, nil
 }
 
-func (d *destinationSysLog) log(t time.Time, level Level, format string, args ...interface{}) {
+func (d *destinationSysLog) log(t time.Time, level Level, format string, args ...any) {
 	d.buf.Reset()
 	writeTime(&d.buf, t, false)
 	writeLevel(&d.buf, level, false)
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
index f2d6cde9..a6a60cc1 100644
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -135,13 +135,13 @@ func writeLevel(buf *bytes.Buffer, level Level, useColor bool) {
 	buf.WriteByte(' ')
 }
 
-func writeContent(buf *bytes.Buffer, format string, args []interface{}) {
+func writeContent(buf *bytes.Buffer, format string, args []any) {
 	fmt.Fprintf(buf, format, args...)
 	buf.WriteByte('\n')
 }
 
 // Log writes a log entry.
-func (lh *Logger) Log(level Level, format string, args ...interface{}) {
+func (lh *Logger) Log(level Level, format string, args ...any) {
 	if level < lh.level {
 		return
 	}
diff --git a/internal/logger/writer.go b/internal/logger/writer.go
index fc16b436..aea8fe57 100644
--- a/internal/logger/writer.go
+++ b/internal/logger/writer.go
@@ -2,5 +2,5 @@ package logger
 
 // Writer is an object that provides a log method.
 type Writer interface {
-	Log(Level, string, ...interface{})
+	Log(Level, string, ...any)
 }
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
index bec4015e..0ad67f8f 100644
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -20,7 +20,7 @@ import (
 	"github.com/bluenviron/mediamtx/internal/protocols/httpp"
 )
 
-func interfaceIsEmpty(i interface{}) bool {
+func interfaceIsEmpty(i any) bool {
 	return reflect.ValueOf(i).Kind() != reflect.Ptr || reflect.ValueOf(i).IsNil()
 }
 
@@ -130,7 +130,7 @@ func (m *Metrics) Close() {
 }
 
 // Log implements logger.Writer.
-func (m *Metrics) Log(level logger.Level, format string, args ...interface{}) {
+func (m *Metrics) Log(level logger.Level, format string, args ...any) {
 	m.Parent.Log(level, "[metrics] "+format, args...)
 }
 
diff --git a/internal/playback/muxer_fmp4.go b/internal/playback/muxer_fmp4.go
index a7ee99e0..1af1806c 100644
--- a/internal/playback/muxer_fmp4.go
+++ b/internal/playback/muxer_fmp4.go
@@ -80,10 +80,7 @@ func (w *muxerFMP4) writeSample(
 				w.curTrack.samples = w.curTrack.samples[:0]
 			}
 		} else {
-			duration := dts - w.curTrack.lastDTS
-			if duration < 0 {
-				duration = 0
-			}
+			duration := max(dts-w.curTrack.lastDTS, 0)
 			w.curTrack.samples[len(w.curTrack.samples)-1].Duration = uint32(duration)
 		}
 
@@ -128,10 +125,7 @@ func (w *muxerFMP4) writeSample(
 
 func (w *muxerFMP4) writeFinalDTS(dts int64) {
 	if len(w.curTrack.samples) != 0 && w.curTrack.firstDTS >= 0 {
-		duration := dts - w.curTrack.lastDTS
-		if duration < 0 {
-			duration = 0
-		}
+		duration := max(dts-w.curTrack.lastDTS, 0)
 		w.curTrack.samples[len(w.curTrack.samples)-1].Duration = uint32(duration)
 	}
 }
diff --git a/internal/playback/muxer_mp4.go b/internal/playback/muxer_mp4.go
index 3677569d..f5b3791e 100644
--- a/internal/playback/muxer_mp4.go
+++ b/internal/playback/muxer_mp4.go
@@ -62,10 +62,7 @@ func (w *muxerMP4) writeSample(
 	if len(w.curTrack.Samples) == 0 {
 		w.curTrack.TimeOffset = int32(dts)
 	} else {
-		duration := dts - w.curTrack.lastDTS
-		if duration < 0 {
-			duration = 0
-		}
+		duration := max(dts-w.curTrack.lastDTS, 0)
 		w.curTrack.Samples[len(w.curTrack.Samples)-1].Duration = uint32(duration)
 	}
 
@@ -87,10 +84,7 @@ func (w *muxerMP4) writeSample(
 
 func (w *muxerMP4) writeFinalDTS(dts int64) {
 	if len(w.curTrack.Samples) != 0 {
-		duration := dts - w.curTrack.lastDTS
-		if duration < 0 {
-			duration = 0
-		}
+		duration := max(dts-w.curTrack.lastDTS, 0)
 		w.curTrack.Samples[len(w.curTrack.Samples)-1].Duration = uint32(duration)
 	}
 }
diff --git a/internal/playback/segment_fmp4.go b/internal/playback/segment_fmp4.go
index 81d3bcd3..b62f5c7d 100644
--- a/internal/playback/segment_fmp4.go
+++ b/internal/playback/segment_fmp4.go
@@ -431,7 +431,7 @@ func segmentFMP4MuxParts(
 	var segmentDuration time.Duration
 	breakAtNextMdat := false
 
-	_, err := amp4.ReadBoxStructure(r, func(h *amp4.ReadHandle) (interface{}, error) {
+	_, err := amp4.ReadBoxStructure(r, func(h *amp4.ReadHandle) (any, error) {
 		switch h.BoxInfo.Type.String() {
 		case "moof":
 			moofOffset = h.BoxInfo.Offset
diff --git a/internal/playback/server.go b/internal/playback/server.go
index ff6747bb..ca930a81 100644
--- a/internal/playback/server.go
+++ b/internal/playback/server.go
@@ -73,7 +73,7 @@ func (s *Server) Close() {
 }
 
 // Log implements logger.Writer.
-func (s *Server) Log(level logger.Level, format string, args ...interface{}) {
+func (s *Server) Log(level logger.Level, format string, args ...any) {
 	s.Parent.Log(level, "[playback] "+format, args...)
 }
 
diff --git a/internal/pprof/pprof.go b/internal/pprof/pprof.go
index 43a94ec1..945a5cc4 100644
--- a/internal/pprof/pprof.go
+++ b/internal/pprof/pprof.go
@@ -76,7 +76,7 @@ func (pp *PPROF) Close() {
 }
 
 // Log implements logger.Writer.
-func (pp *PPROF) Log(level logger.Level, format string, args ...interface{}) {
+func (pp *PPROF) Log(level logger.Level, format string, args ...any) {
 	pp.Parent.Log(level, "[pprof] "+format, args...)
 }
 
diff --git a/internal/protocols/rtmp/from_stream.go b/internal/protocols/rtmp/from_stream.go
index 91cccb22..4abeee7a 100644
--- a/internal/protocols/rtmp/from_stream.go
+++ b/internal/protocols/rtmp/from_stream.go
@@ -51,7 +51,7 @@ func FromStream(
 		for _, forma := range media.Formats {
 			switch forma := forma.(type) {
 			case *format.AV1:
-				if slices.Contains(conn.FourCcList, interface{}(fourCCToString(message.FourCCAV1))) {
+				if slices.Contains(conn.FourCcList, any(fourCCToString(message.FourCCAV1))) {
 					r.OnData(
 						media,
 						forma,
@@ -71,7 +71,7 @@ func FromStream(
 				}
 
 			case *format.VP9:
-				if slices.Contains(conn.FourCcList, interface{}(fourCCToString(message.FourCCVP9))) {
+				if slices.Contains(conn.FourCcList, any(fourCCToString(message.FourCCVP9))) {
 					r.OnData(
 						media,
 						forma,
@@ -91,7 +91,7 @@ func FromStream(
 				}
 
 			case *format.H265:
-				if slices.Contains(conn.FourCcList, interface{}(fourCCToString(message.FourCCHEVC))) {
+				if slices.Contains(conn.FourCcList, any(fourCCToString(message.FourCCHEVC))) {
 					var videoDTSExtractor *h265.DTSExtractor
 
 					r.OnData(
@@ -179,7 +179,7 @@ func FromStream(
 				tracks = append(tracks, forma)
 
 			case *format.Opus:
-				if slices.Contains(conn.FourCcList, interface{}(fourCCToString(message.FourCCOpus))) {
+				if slices.Contains(conn.FourCcList, any(fourCCToString(message.FourCCOpus))) {
 					r.OnData(
 						media,
 						forma,
@@ -303,7 +303,7 @@ func FromStream(
 				tracks = append(tracks, forma)
 
 			case *format.AC3:
-				if slices.Contains(conn.FourCcList, interface{}(fourCCToString(message.FourCCAC3))) {
+				if slices.Contains(conn.FourCcList, any(fourCCToString(message.FourCCAC3))) {
 					r.OnData(
 						media,
 						forma,
diff --git a/internal/protocols/webrtc/from_stream.go b/internal/protocols/webrtc/from_stream.go
index f950f9e5..dbd125ad 100644
--- a/internal/protocols/webrtc/from_stream.go
+++ b/internal/protocols/webrtc/from_stream.go
@@ -27,6 +27,15 @@ const (
 	webrtcPayloadMaxSize = 1188 // 1200 - 12 (RTP header)
 )
 
+var multichannelOpusSDP = map[int]string{
+	3: "channel_mapping=0,2,1;num_streams=2;coupled_streams=1",
+	4: "channel_mapping=0,1,2,3;num_streams=2;coupled_streams=2",
+	5: "channel_mapping=0,4,1,2,3;num_streams=3;coupled_streams=2",
+	6: "channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2",
+	7: "channel_mapping=0,4,1,2,3,5,6;num_streams=4;coupled_streams=4",
+	8: "channel_mapping=0,6,1,4,5,2,3,7;num_streams=5;coupled_streams=4",
+}
+
 var errNoSupportedCodecsFrom = errors.New(
 	"the stream doesn't contain any supported codec, which are currently " +
 		"AV1, VP9, VP8, H265, H264, Opus, G722, G711, LPCM")
diff --git a/internal/protocols/webrtc/incoming_track.go b/internal/protocols/webrtc/incoming_track.go
index 8cb4dbe2..6cdef34c 100644
--- a/internal/protocols/webrtc/incoming_track.go
+++ b/internal/protocols/webrtc/incoming_track.go
@@ -1,7 +1,6 @@
 package webrtc
 
 import (
-	"sync/atomic"
 	"time"
 
 	"github.com/bluenviron/gortsplib/v5/pkg/rtpreceiver"
@@ -239,12 +238,10 @@ var incomingAudioCodecs = []webrtc.RTPCodecParameters{
 type IncomingTrack struct {
 	OnPacketRTP func(*rtp.Packet)
 
-	track              *webrtc.TrackRemote
-	receiver           *webrtc.RTPReceiver
-	writeRTCP          func([]rtcp.Packet) error
-	log                logger.Writer
-	rtpPacketsReceived *uint64
-	rtpPacketsLost     *uint64
+	track     *webrtc.TrackRemote
+	receiver  *webrtc.RTPReceiver
+	writeRTCP func([]rtcp.Packet) error
+	log       logger.Writer
 
 	packetsLost *counterdumper.CounterDumper
 	rtpReceiver *rtpreceiver.Receiver
@@ -347,19 +344,13 @@ func (t *IncomingTrack) start() {
 				return
 			}
 
-			packets, lost, err2 := t.rtpReceiver.ProcessPacket(pkt, time.Now(), true)
-			if err2 != nil {
-				t.log.Log(logger.Warn, err2.Error())
-				continue
-			}
+			packets, lost := t.rtpReceiver.ProcessPacket2(pkt, time.Now(), true)
+
 			if lost != 0 {
-				atomic.AddUint64(t.rtpPacketsLost, lost)
 				t.packetsLost.Add(lost)
 				// do not return
 			}
 
-			atomic.AddUint64(t.rtpPacketsReceived, uint64(len(packets)))
-
 			for _, pkt := range packets {
 				// sometimes Chrome sends empty RTP packets. ignore them.
 				if len(pkt.Payload) == 0 {
diff --git a/internal/protocols/webrtc/outgoing_track.go b/internal/protocols/webrtc/outgoing_track.go
index b1b63120..fad9c1dd 100644
--- a/internal/protocols/webrtc/outgoing_track.go
+++ b/internal/protocols/webrtc/outgoing_track.go
@@ -2,7 +2,6 @@ package webrtc
 
 import (
 	"strings"
-	"sync/atomic"
 	"time"
 
 	"github.com/bluenviron/gortsplib/v5/pkg/rtpsender"
@@ -11,23 +10,13 @@ import (
 	"github.com/pion/webrtc/v4"
 )
 
-var multichannelOpusSDP = map[int]string{
-	3: "channel_mapping=0,2,1;num_streams=2;coupled_streams=1",
-	4: "channel_mapping=0,1,2,3;num_streams=2;coupled_streams=2",
-	5: "channel_mapping=0,4,1,2,3;num_streams=3;coupled_streams=2",
-	6: "channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2",
-	7: "channel_mapping=0,4,1,2,3,5,6;num_streams=4;coupled_streams=4",
-	8: "channel_mapping=0,6,1,4,5,2,3,7;num_streams=5;coupled_streams=4",
-}
-
 // OutgoingTrack is a WebRTC outgoing track
 type OutgoingTrack struct {
 	Caps webrtc.RTPCodecCapability
 
-	track          *webrtc.TrackLocalStaticRTP
-	ssrc           uint32
-	rtcpSender     *rtpsender.Sender
-	rtpPacketsSent *uint64
+	track      *webrtc.TrackLocalStaticRTP
+	ssrc       uint32
+	rtcpSender *rtpsender.Sender
 }
 
 func (t *OutgoingTrack) isVideo() bool {
@@ -69,8 +58,6 @@ func (t *OutgoingTrack) setup(p *PeerConnection) error {
 	}
 	t.rtcpSender.Initialize()
 
-	t.rtpPacketsSent = p.rtpPacketsSent
-
 	// incoming RTCP packets must always be read to make interceptors work
 	go func() {
 		buf := make([]byte, 1500)
@@ -108,7 +95,5 @@ func (t *OutgoingTrack) WriteRTPWithNTP(pkt *rtp.Packet, ntp time.Time) error {
 
 	t.rtcpSender.ProcessPacket(pkt, ntp, true)
 
-	atomic.AddUint64(t.rtpPacketsSent, 1)
-
 	return t.track.WriteRTP(pkt)
 }
diff --git a/internal/protocols/webrtc/peer_connection.go b/internal/protocols/webrtc/peer_connection.go
index 7b34037e..b43f26be 100644
--- a/internal/protocols/webrtc/peer_connection.go
+++ b/internal/protocols/webrtc/peer_connection.go
@@ -147,15 +147,12 @@ type PeerConnection struct {
 	OutgoingTracks        []*OutgoingTrack
 	Log                   logger.Writer
 
-	wr                 *webrtc.PeerConnection
-	ctx                context.Context
-	ctxCancel          context.CancelFunc
-	incomingTracks     []*IncomingTrack
-	startedReading     *int64
-	rtpPacketsReceived *uint64
-	rtpPacketsSent     *uint64
-	rtpPacketsLost     *uint64
-	statsInterceptor   *statsInterceptor
+	wr               *webrtc.PeerConnection
+	ctx              context.Context
+	ctxCancel        context.CancelFunc
+	incomingTracks   []*IncomingTrack
+	startedReading   *int64
+	statsInterceptor *statsInterceptor
 
 	newLocalCandidate chan *webrtc.ICECandidateInit
 	incomingTrack     chan trackRecvPair
@@ -303,9 +300,6 @@ func (co *PeerConnection) Start() error {
 	co.ctx, co.ctxCancel = context.WithCancel(context.Background())
 
 	co.startedReading = new(int64)
-	co.rtpPacketsReceived = new(uint64)
-	co.rtpPacketsSent = new(uint64)
-	co.rtpPacketsLost = new(uint64)
 
 	co.newLocalCandidate = make(chan *webrtc.ICECandidateInit)
 	co.connected = make(chan struct{})
@@ -707,12 +701,10 @@ func (co *PeerConnection) GatherIncomingTracks() error {
 
 		case pair := <-co.incomingTrack:
 			t := &IncomingTrack{
-				track:              pair.track,
-				receiver:           pair.receiver,
-				writeRTCP:          co.wr.WriteRTCP,
-				log:                co.Log,
-				rtpPacketsReceived: co.rtpPacketsReceived,
-				rtpPacketsLost:     co.rtpPacketsLost,
+				track:     pair.track,
+				receiver:  pair.receiver,
+				writeRTCP: co.wr.WriteRTCP,
+				log:       co.Log,
 			}
 			t.initialize()
 			co.incomingTracks = append(co.incomingTracks, t)
@@ -810,16 +802,27 @@ func (co *PeerConnection) Stats() *Stats {
 
 	v := float64(0)
 	n := float64(0)
+	packetsReceived := uint64(0)
+	packetsSent := uint64(0)
+	packetsLost := uint64(0)
 
 	if atomic.LoadInt64(co.startedReading) == 1 {
 		for _, tr := range co.incomingTracks {
 			if recvStats := tr.rtpReceiver.Stats(); recvStats != nil {
 				v += recvStats.Jitter
 				n++
+				packetsReceived += recvStats.TotalReceived
+				packetsLost += recvStats.TotalLost
 			}
 		}
 	}
 
+	for _, tr := range co.OutgoingTracks {
+		if sentStats := tr.rtcpSender.Stats(); sentStats != nil {
+			packetsSent += sentStats.TotalSent
+		}
+	}
+
 	var rtpPacketsJitter float64
 	if n != 0 {
 		rtpPacketsJitter = v / n
@@ -830,9 +833,9 @@ func (co *PeerConnection) Stats() *Stats {
 	return &Stats{
 		BytesReceived:       bytesReceived,
 		BytesSent:           bytesSent,
-		RTPPacketsReceived:  atomic.LoadUint64(co.rtpPacketsReceived),
-		RTPPacketsSent:      atomic.LoadUint64(co.rtpPacketsSent),
-		RTPPacketsLost:      atomic.LoadUint64(co.rtpPacketsLost),
+		RTPPacketsReceived:  packetsReceived,
+		RTPPacketsSent:      packetsSent,
+		RTPPacketsLost:      packetsLost,
 		RTPPacketsJitter:    rtpPacketsJitter,
 		RTCPPacketsReceived: atomic.LoadUint64(co.statsInterceptor.rtcpPacketsReceived),
 		RTCPPacketsSent:     atomic.LoadUint64(co.statsInterceptor.rtcpPacketsSent),
diff --git a/internal/protocols/websocket/serverconn.go b/internal/protocols/websocket/serverconn.go
index c5fd3506..3dd59c39 100644
--- a/internal/protocols/websocket/serverconn.go
+++ b/internal/protocols/websocket/serverconn.go
@@ -95,12 +95,12 @@ func (c *ServerConn) run() {
 }
 
 // ReadJSON reads a JSON object.
-func (c *ServerConn) ReadJSON(in interface{}) error {
+func (c *ServerConn) ReadJSON(in any) error {
 	return c.wc.ReadJSON(in)
 }
 
 // WriteJSON writes a JSON object.
-func (c *ServerConn) WriteJSON(in interface{}) error {
+func (c *ServerConn) WriteJSON(in any) error {
 	byts, err := json.Marshal(in)
 	if err != nil {
 		return err
diff --git a/internal/recordcleaner/cleaner.go b/internal/recordcleaner/cleaner.go
index 3b41b8a2..98de570f 100644
--- a/internal/recordcleaner/cleaner.go
+++ b/internal/recordcleaner/cleaner.go
@@ -44,7 +44,7 @@ func (c *Cleaner) Close() {
 }
 
 // Log implements logger.Writer.
-func (c *Cleaner) Log(level logger.Level, format string, args ...interface{}) {
+func (c *Cleaner) Log(level logger.Level, format string, args ...any) {
 	c.Parent.Log(level, "[record cleaner]"+format, args...)
 }
 
diff --git a/internal/recorder/recorder.go b/internal/recorder/recorder.go
index c76c217d..4c93b3e1 100644
--- a/internal/recorder/recorder.go
+++ b/internal/recorder/recorder.go
@@ -71,7 +71,7 @@ func (r *Recorder) Initialize() {
 }
 
 // Log implements logger.Writer.
-func (r *Recorder) Log(level logger.Level, format string, args ...interface{}) {
+func (r *Recorder) Log(level logger.Level, format string, args ...any) {
 	r.Parent.Log(level, "[recorder] "+format, args...)
 }
 
diff --git a/internal/recorder/recorder_instance.go b/internal/recorder/recorder_instance.go
index 899ae6ee..c0b3e06f 100644
--- a/internal/recorder/recorder_instance.go
+++ b/internal/recorder/recorder_instance.go
@@ -42,7 +42,7 @@ type recorderInstance struct {
 }
 
 // Log implements logger.Writer.
-func (ri *recorderInstance) Log(level logger.Level, format string, args ...interface{}) {
+func (ri *recorderInstance) Log(level logger.Level, format string, args ...any) {
 	ri.parent.Log(level, format, args...)
 }
 
diff --git a/internal/servers/hls/http_server.go b/internal/servers/hls/http_server.go
index efb56638..ac5436ee 100644
--- a/internal/servers/hls/http_server.go
+++ b/internal/servers/hls/http_server.go
@@ -76,7 +76,7 @@ func (s *httpServer) initialize() error {
 }
 
 // Log implements logger.Writer.
-func (s *httpServer) Log(level logger.Level, format string, args ...interface{}) {
+func (s *httpServer) Log(level logger.Level, format string, args ...any) {
 	s.parent.Log(level, format, args...)
 }
 
diff --git a/internal/servers/hls/muxer.go b/internal/servers/hls/muxer.go
index f335f513..122f5546 100644
--- a/internal/servers/hls/muxer.go
+++ b/internal/servers/hls/muxer.go
@@ -98,8 +98,8 @@ func (m *muxer) Close() {
 }
 
 // Log implements logger.Writer.
-func (m *muxer) Log(level logger.Level, format string, args ...interface{}) {
-	m.parent.Log(level, "[muxer %s] "+format, append([]interface{}{m.pathName}, args...)...)
+func (m *muxer) Log(level logger.Level, format string, args ...any) {
+	m.parent.Log(level, "[muxer %s] "+format, append([]any{m.pathName}, args...)...)
 }
 
 // PathName returns the path name.
diff --git a/internal/servers/hls/muxer_instance.go b/internal/servers/hls/muxer_instance.go
index ff6e80c4..2e7a0e76 100644
--- a/internal/servers/hls/muxer_instance.go
+++ b/internal/servers/hls/muxer_instance.go
@@ -73,7 +73,7 @@ func (mi *muxerInstance) initialize() error {
 }
 
 // Log implements logger.Writer.
-func (mi *muxerInstance) Log(level logger.Level, format string, args ...interface{}) {
+func (mi *muxerInstance) Log(level logger.Level, format string, args ...any) {
 	mi.parent.Log(level, format, args...)
 }
 
diff --git a/internal/servers/hls/server.go b/internal/servers/hls/server.go
index b443bb46..74175af9 100644
--- a/internal/servers/hls/server.go
+++ b/internal/servers/hls/server.go
@@ -18,7 +18,7 @@ import (
 // ErrMuxerNotFound is returned when a muxer is not found.
 var ErrMuxerNotFound = errors.New("muxer not found")
 
-func interfaceIsEmpty(i interface{}) bool {
+func interfaceIsEmpty(i any) bool {
 	return reflect.ValueOf(i).Kind() != reflect.Ptr || reflect.ValueOf(i).IsNil()
 }
 
@@ -150,7 +150,7 @@ func (s *Server) Initialize() error {
 }
 
 // Log implements logger.Writer.
-func (s *Server) Log(level logger.Level, format string, args ...interface{}) {
+func (s *Server) Log(level logger.Level, format string, args ...any) {
 	s.Parent.Log(level, "[HLS] "+format, args...)
 }
 
diff --git a/internal/servers/rtmp/conn.go b/internal/servers/rtmp/conn.go
index e662dc4a..14b738dd 100644
--- a/internal/servers/rtmp/conn.go
+++ b/internal/servers/rtmp/conn.go
@@ -71,8 +71,8 @@ func (c *conn) remoteAddr() net.Addr {
 }
 
 // Log implements logger.Writer.
-func (c *conn) Log(level logger.Level, format string, args ...interface{}) {
-	c.parent.Log(level, "[conn %v] "+format, append([]interface{}{c.nconn.RemoteAddr()}, args...)...)
+func (c *conn) Log(level logger.Level, format string, args ...any) {
+	c.parent.Log(level, "[conn %v] "+format, append([]any{c.nconn.RemoteAddr()}, args...)...)
 }
 
 func (c *conn) ip() net.IP {
diff --git a/internal/servers/rtmp/server.go b/internal/servers/rtmp/server.go
index 048c9c30..a545c93b 100644
--- a/internal/servers/rtmp/server.go
+++ b/internal/servers/rtmp/server.go
@@ -25,7 +25,7 @@ import (
 // ErrConnNotFound is returned when a connection is not found.
 var ErrConnNotFound = errors.New("connection not found")
 
-func interfaceIsEmpty(i interface{}) bool {
+func interfaceIsEmpty(i any) bool {
 	return reflect.ValueOf(i).Kind() != reflect.Ptr || reflect.ValueOf(i).IsNil()
 }
 
@@ -163,14 +163,14 @@ func (s *Server) Initialize() error {
 }
 
 // Log implements logger.Writer.
-func (s *Server) Log(level logger.Level, format string, args ...interface{}) {
+func (s *Server) Log(level logger.Level, format string, args ...any) {
 	label := func() string {
 		if s.IsTLS {
 			return "RTMPS"
 		}
 		return "RTMP"
 	}()
-	s.Parent.Log(level, "[%s] "+format, append([]interface{}{label}, args...)...)
+	s.Parent.Log(level, "[%s] "+format, append([]any{label}, args...)...)
 }
 
 // Close closes the server.
diff --git a/internal/servers/rtsp/conn.go b/internal/servers/rtsp/conn.go
index 9d3e51e4..9966c6ea 100644
--- a/internal/servers/rtsp/conn.go
+++ b/internal/servers/rtsp/conn.go
@@ -100,8 +100,8 @@ func (c *conn) initialize() {
 }
 
 // Log implements logger.Writer.
-func (c *conn) Log(level logger.Level, format string, args ...interface{}) {
-	c.parent.Log(level, "[conn %v] "+format, append([]interface{}{c.rconn.NetConn().RemoteAddr()}, args...)...)
+func (c *conn) Log(level logger.Level, format string, args ...any) {
+	c.parent.Log(level, "[conn %v] "+format, append([]any{c.rconn.NetConn().RemoteAddr()}, args...)...)
 }
 
 // Conn returns the RTSP connection.
diff --git a/internal/servers/rtsp/server.go b/internal/servers/rtsp/server.go
index ecc2fc37..f1503af0 100644
--- a/internal/servers/rtsp/server.go
+++ b/internal/servers/rtsp/server.go
@@ -32,7 +32,7 @@ var ErrConnNotFound = errors.New("connection not found")
 // ErrSessionNotFound is returned when a session is not found.
 var ErrSessionNotFound = errors.New("session not found")
 
-func interfaceIsEmpty(i interface{}) bool {
+func interfaceIsEmpty(i any) bool {
 	return reflect.ValueOf(i).Kind() != reflect.Ptr || reflect.ValueOf(i).IsNil()
 }
 
@@ -170,14 +170,14 @@ func (s *Server) Initialize() error {
 }
 
 // Log implements logger.Writer.
-func (s *Server) Log(level logger.Level, format string, args ...interface{}) {
+func (s *Server) Log(level logger.Level, format string, args ...any) {
 	label := func() string {
 		if s.IsTLS {
 			return "RTSPS"
 		}
 		return "RTSP"
 	}()
-	s.Parent.Log(level, "[%s] "+format, append([]interface{}{label}, args...)...)
+	s.Parent.Log(level, "[%s] "+format, append([]any{label}, args...)...)
 }
 
 // Close closes the server.
diff --git a/internal/servers/rtsp/session.go b/internal/servers/rtsp/session.go
index 7e4100ea..1cf63907 100644
--- a/internal/servers/rtsp/session.go
+++ b/internal/servers/rtsp/session.go
@@ -117,9 +117,9 @@ func (s *session) remoteAddr() net.Addr {
 }
 
 // Log implements logger.Writer.
-func (s *session) Log(level logger.Level, format string, args ...interface{}) {
+func (s *session) Log(level logger.Level, format string, args ...any) {
 	id := hex.EncodeToString(s.uuid[:4])
-	s.parent.Log(level, "[session %s] "+format, append([]interface{}{id}, args...)...)
+	s.parent.Log(level, "[session %s] "+format, append([]any{id}, args...)...)
 }
 
 // onClose is called by rtspServer.
diff --git a/internal/servers/srt/conn.go b/internal/servers/srt/conn.go
index 9ae931da..a15cd317 100644
--- a/internal/servers/srt/conn.go
+++ b/internal/servers/srt/conn.go
@@ -85,8 +85,8 @@ func (c *conn) Close() {
 }
 
 // Log implements logger.Writer.
-func (c *conn) Log(level logger.Level, format string, args ...interface{}) {
-	c.parent.Log(level, "[conn %v] "+format, append([]interface{}{c.connReq.RemoteAddr()}, args...)...)
+func (c *conn) Log(level logger.Level, format string, args ...any) {
+	c.parent.Log(level, "[conn %v] "+format, append([]any{c.connReq.RemoteAddr()}, args...)...)
 }
 
 func (c *conn) ip() net.IP {
diff --git a/internal/servers/srt/server.go b/internal/servers/srt/server.go
index 171cc624..583c15ee 100644
--- a/internal/servers/srt/server.go
+++ b/internal/servers/srt/server.go
@@ -23,7 +23,7 @@ import (
 // ErrConnNotFound is returned when a connection is not found.
 var ErrConnNotFound = errors.New("connection not found")
 
-func interfaceIsEmpty(i interface{}) bool {
+func interfaceIsEmpty(i any) bool {
 	return reflect.ValueOf(i).Kind() != reflect.Ptr || reflect.ValueOf(i).IsNil()
 }
 
@@ -145,7 +145,7 @@ func (s *Server) Initialize() error {
 }
 
 // Log implements logger.Writer.
-func (s *Server) Log(level logger.Level, format string, args ...interface{}) {
+func (s *Server) Log(level logger.Level, format string, args ...any) {
 	s.Parent.Log(level, "[SRT] "+format, args...)
 }
 
diff --git a/internal/servers/srt/streamid.go b/internal/servers/srt/streamid.go
index 54b949ab..506b9c9c 100644
--- a/internal/servers/srt/streamid.go
+++ b/internal/servers/srt/streamid.go
@@ -24,7 +24,7 @@ func (s *streamID) unmarshal(raw string) error {
 	// standard syntax
 	// https://github.com/Haivision/srt/blob/master/docs/features/access-control.md
 	if strings.HasPrefix(raw, "#!::") {
-		for _, kv := range strings.Split(raw[len("#!::"):], ",") {
+		for kv := range strings.SplitSeq(raw[len("#!::"):], ",") {
 			kv2 := strings.SplitN(kv, "=", 2)
 			if len(kv2) != 2 {
 				return fmt.Errorf("invalid value")
diff --git a/internal/servers/webrtc/http_server.go b/internal/servers/webrtc/http_server.go
index b75b3b8b..fb7332c2 100644
--- a/internal/servers/webrtc/http_server.go
+++ b/internal/servers/webrtc/http_server.go
@@ -113,7 +113,7 @@ func (s *httpServer) initialize() error {
 }
 
 // Log implements logger.Writer.
-func (s *httpServer) Log(level logger.Level, format string, args ...interface{}) {
+func (s *httpServer) Log(level logger.Level, format string, args ...any) {
 	s.parent.Log(level, format, args...)
 }
 
diff --git a/internal/servers/webrtc/server.go b/internal/servers/webrtc/server.go
index a18f9c24..1851d12e 100644
--- a/internal/servers/webrtc/server.go
+++ b/internal/servers/webrtc/server.go
@@ -39,7 +39,7 @@ const (
 // ErrSessionNotFound is returned when a session is not found.
 var ErrSessionNotFound = errors.New("session not found")
 
-func interfaceIsEmpty(i interface{}) bool {
+func interfaceIsEmpty(i any) bool {
 	return reflect.ValueOf(i).Kind() != reflect.Ptr || reflect.ValueOf(i).IsNil()
 }
 
@@ -324,7 +324,7 @@ func (s *Server) Initialize() error {
 }
 
 // Log implements logger.Writer.
-func (s *Server) Log(level logger.Level, format string, args ...interface{}) {
+func (s *Server) Log(level logger.Level, format string, args ...any) {
 	s.Parent.Log(level, "[WebRTC] "+format, args...)
 }
 
diff --git a/internal/servers/webrtc/session.go b/internal/servers/webrtc/session.go
index 634f17b5..183385ee 100644
--- a/internal/servers/webrtc/session.go
+++ b/internal/servers/webrtc/session.go
@@ -88,9 +88,9 @@ func (s *session) initialize() {
 }
 
 // Log implements logger.Writer.
-func (s *session) Log(level logger.Level, format string, args ...interface{}) {
+func (s *session) Log(level logger.Level, format string, args ...any) {
 	id := hex.EncodeToString(s.uuid[:4])
-	s.parent.Log(level, "[session %v] "+format, append([]interface{}{id}, args...)...)
+	s.parent.Log(level, "[session %v] "+format, append([]any{id}, args...)...)
 }
 
 func (s *session) Close() {
diff --git a/internal/staticsources/handler.go b/internal/staticsources/handler.go
index 97bb299a..49f7e698 100644
--- a/internal/staticsources/handler.go
+++ b/internal/staticsources/handler.go
@@ -211,7 +211,7 @@ func (s *Handler) Stop(reason string) {
 }
 
 // Log implements logger.Writer.
-func (s *Handler) Log(level logger.Level, format string, args ...interface{}) {
+func (s *Handler) Log(level logger.Level, format string, args ...any) {
 	s.Parent.Log(level, format, args...)
 }
 
diff --git a/internal/staticsources/hls/source.go b/internal/staticsources/hls/source.go
index c9848739..ff7ba90b 100644
--- a/internal/staticsources/hls/source.go
+++ b/internal/staticsources/hls/source.go
@@ -31,7 +31,7 @@ type Source struct {
 }
 
 // Log implements logger.Writer.
-func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
+func (s *Source) Log(level logger.Level, format string, args ...any) {
 	s.Parent.Log(level, "[HLS source] "+format, args...)
 }
 
diff --git a/internal/staticsources/mpegts/source.go b/internal/staticsources/mpegts/source.go
index 4e41d0e1..c13cec82 100644
--- a/internal/staticsources/mpegts/source.go
+++ b/internal/staticsources/mpegts/source.go
@@ -33,7 +33,7 @@ type Source struct {
 }
 
 // Log implements logger.Writer.
-func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
+func (s *Source) Log(level logger.Level, format string, args ...any) {
 	s.Parent.Log(level, "[MPEG-TS source] "+format, args...)
 }
 
diff --git a/internal/staticsources/rpicamera/source.go b/internal/staticsources/rpicamera/source.go
index 96116354..8c3ab90c 100644
--- a/internal/staticsources/rpicamera/source.go
+++ b/internal/staticsources/rpicamera/source.go
@@ -115,7 +115,7 @@ type Source struct {
 }
 
 // Log implements logger.Writer.
-func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
+func (s *Source) Log(level logger.Level, format string, args ...any) {
 	s.Parent.Log(level, "[RPI Camera source] "+format, args...)
 }
 
diff --git a/internal/staticsources/rtmp/source.go b/internal/staticsources/rtmp/source.go
index 60aff126..9fff458e 100644
--- a/internal/staticsources/rtmp/source.go
+++ b/internal/staticsources/rtmp/source.go
@@ -33,7 +33,7 @@ type Source struct {
 }
 
 // Log implements logger.Writer.
-func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
+func (s *Source) Log(level logger.Level, format string, args ...any) {
 	s.Parent.Log(level, "[RTMP source] "+format, args...)
 }
 
diff --git a/internal/staticsources/rtp/format.go b/internal/staticsources/rtp/format.go
new file mode 100644
index 00000000..753efe7f
--- /dev/null
+++ b/internal/staticsources/rtp/format.go
@@ -0,0 +1,25 @@
+package rtp
+
+import (
+	"time"
+
+	"github.com/bluenviron/gortsplib/v5/pkg/format"
+	"github.com/bluenviron/gortsplib/v5/pkg/rtpreceiver"
+	"github.com/pion/rtcp"
+)
+
+type rtpFormat struct {
+	desc format.Format
+
+	rtpReceiver *rtpreceiver.Receiver
+}
+
+func (f *rtpFormat) initialize() {
+	f.rtpReceiver = &rtpreceiver.Receiver{
+		ClockRate:            f.desc.ClockRate(),
+		UnrealiableTransport: true,
+		Period:               10 * time.Second,
+		WritePacketRTCP: func(_ rtcp.Packet) {
+		},
+	}
+}
diff --git a/internal/staticsources/rtp/media.go b/internal/staticsources/rtp/media.go
new file mode 100644
index 00000000..8fdc744d
--- /dev/null
+++ b/internal/staticsources/rtp/media.go
@@ -0,0 +1,7 @@
+package rtp
+
+import "github.com/bluenviron/gortsplib/v5/pkg/description"
+
+type rtpMedia struct {
+	desc *description.Media
+}
diff --git a/internal/staticsources/rtp/source.go b/internal/staticsources/rtp/source.go
index ac23aeb7..8e3dc21d 100644
--- a/internal/staticsources/rtp/source.go
+++ b/internal/staticsources/rtp/source.go
@@ -8,7 +8,6 @@ import (
 	"time"
 
 	"github.com/bluenviron/gortsplib/v5/pkg/description"
-	"github.com/bluenviron/gortsplib/v5/pkg/format"
 	"github.com/bluenviron/gortsplib/v5/pkg/rtptime"
 	"github.com/bluenviron/gortsplib/v5/pkg/sdp"
 	"github.com/bluenviron/mediamtx/internal/conf"
@@ -35,7 +34,7 @@ type Source struct {
 }
 
 // Log implements logger.Writer.
-func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
+func (s *Source) Log(level logger.Level, format string, args ...any) {
 	s.Parent.Log(level, "[RTP source] "+format, args...)
 }
 
@@ -103,6 +102,22 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
 }
 
 func (s *Source) runReader(desc *description.Session, nc net.Conn) error {
+	packetsLost := &counterdumper.CounterDumper{
+		OnReport: func(val uint64) {
+			s.Log(logger.Warn, "%d RTP %s lost",
+				val,
+				func() string {
+					if val == 1 {
+						return "packet"
+					}
+					return "packets"
+				}())
+		},
+	}
+
+	packetsLost.Start()
+	defer packetsLost.Stop()
+
 	decodeErrors := &counterdumper.CounterDumper{
 		OnReport: func(val uint64) {
 			s.Log(logger.Warn, "%d decode %s",
@@ -123,13 +138,22 @@ func (s *Source) runReader(desc *description.Session, nc net.Conn) error {
 	timeDecoder := &rtptime.GlobalDecoder{}
 	timeDecoder.Initialize()
 
-	mediasByPayloadType := make(map[uint8]*description.Media)
-	formatsByPayloadType := make(map[uint8]format.Format)
+	mediasByPayloadType := make(map[uint8]*rtpMedia)
+	formatsByPayloadType := make(map[uint8]*rtpFormat)
 
-	for _, media := range desc.Medias {
-		for _, forma := range media.Formats {
-			mediasByPayloadType[forma.PayloadType()] = media
-			formatsByPayloadType[forma.PayloadType()] = forma
+	for _, descMedia := range desc.Medias {
+		rtpMedia := &rtpMedia{
+			desc: descMedia,
+		}
+
+		for _, descFormat := range descMedia.Formats {
+			rtpFormat := &rtpFormat{
+				desc: descFormat,
+			}
+			rtpFormat.initialize()
+
+			mediasByPayloadType[descFormat.PayloadType()] = rtpMedia
+			formatsByPayloadType[descFormat.PayloadType()] = rtpFormat
 		}
 	}
 
@@ -173,12 +197,20 @@ func (s *Source) runReader(desc *description.Session, nc net.Conn) error {
 
 		forma := formatsByPayloadType[pkt.PayloadType]
 
-		pts, ok := timeDecoder.Decode(forma, &pkt)
-		if !ok {
-			continue
+		pkts, lost := forma.rtpReceiver.ProcessPacket2(&pkt, time.Now(), forma.desc.PTSEqualsDTS(&pkt))
+
+		if lost != 0 {
+			packetsLost.Add(lost)
 		}
 
-		stream.WriteRTPPacket(media, forma, &pkt, time.Time{}, pts)
+		for _, pkt := range pkts {
+			pts, ok2 := timeDecoder.Decode(forma.desc, pkt)
+			if !ok2 {
+				continue
+			}
+
+			stream.WriteRTPPacket(media.desc, forma.desc, pkt, time.Time{}, pts)
+		}
 	}
 }
 
diff --git a/internal/staticsources/rtsp/source.go b/internal/staticsources/rtsp/source.go
index edfadbb0..23d16b10 100644
--- a/internal/staticsources/rtsp/source.go
+++ b/internal/staticsources/rtsp/source.go
@@ -79,7 +79,7 @@ type Source struct {
 }
 
 // Log implements logger.Writer.
-func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
+func (s *Source) Log(level logger.Level, format string, args ...any) {
 	s.Parent.Log(level, "[RTSP source] "+format, args...)
 }
 
diff --git a/internal/staticsources/srt/source.go b/internal/staticsources/srt/source.go
index 12d1435b..5e091750 100644
--- a/internal/staticsources/srt/source.go
+++ b/internal/staticsources/srt/source.go
@@ -28,7 +28,7 @@ type Source struct {
 }
 
 // Log implements logger.Writer.
-func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
+func (s *Source) Log(level logger.Level, format string, args ...any) {
 	s.Parent.Log(level, "[SRT source] "+format, args...)
 }
 
diff --git a/internal/staticsources/webrtc/source.go b/internal/staticsources/webrtc/source.go
index 836a874d..2ecbd9aa 100644
--- a/internal/staticsources/webrtc/source.go
+++ b/internal/staticsources/webrtc/source.go
@@ -33,7 +33,7 @@ type Source struct {
 }
 
 // Log implements logger.Writer.
-func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
+func (s *Source) Log(level logger.Level, format string, args ...any) {
 	s.Parent.Log(level, "[WebRTC source] "+format, args...)
 }
 



Test patch:
diff --git a/internal/api/api_test.go b/internal/api/api_test.go
index cf1c3c57..d216e52b 100644
--- a/internal/api/api_test.go
+++ b/internal/api/api_test.go
@@ -20,10 +20,10 @@ import (
 )
 
 type testParent struct {
-	log func(_ logger.Level, _ string, _ ...interface{})
+	log func(_ logger.Level, _ string, _ ...any)
 }
 
-func (p testParent) Log(l logger.Level, s string, a ...interface{}) {
+func (p testParent) Log(l logger.Level, s string, a ...any) {
 	if p.log != nil {
 		p.log(l, s, a...)
 	}
@@ -42,7 +42,7 @@ func tempConf(t *testing.T, cnt string) *conf.Conf {
 	return cnf
 }
 
-func httpRequest(t *testing.T, hc *http.Client, method string, ur string, in interface{}, out interface{}) {
+func httpRequest(t *testing.T, hc *http.Client, method string, ur string, in any, out any) {
 	buf := func() io.Reader {
 		if in == nil {
 			return nil
@@ -74,10 +74,10 @@ func httpRequest(t *testing.T, hc *http.Client, method string, ur string, in int
 }
 
 func checkError(t *testing.T, msg string, body io.Reader) {
-	var resErr map[string]interface{}
+	var resErr map[string]any
 	err := json.NewDecoder(body).Decode(&resErr)
 	require.NoError(t, err)
-	require.Equal(t, map[string]interface{}{"error": msg}, resErr)
+	require.Equal(t, map[string]any{"error": msg}, resErr)
 }
 
 func TestPreflightRequest(t *testing.T) {
@@ -139,9 +139,9 @@ func TestInfo(t *testing.T) {
 	defer tr.CloseIdleConnections()
 	hc := &http.Client{Transport: tr}
 
-	var out map[string]interface{}
+	var out map[string]any
 	httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/info", nil, &out)
-	require.Equal(t, map[string]interface{}{
+	require.Equal(t, map[string]any{
 		"started": time.Date(2008, 11, 7, 11, 22, 0, 0, time.Local).Format(time.RFC3339),
 		"version": "v1.2.3",
 	}, out)
@@ -175,7 +175,7 @@ func TestConfigGlobalGet(t *testing.T) {
 	defer tr.CloseIdleConnections()
 	hc := &http.Client{Transport: tr}
 
-	var out map[string]interface{}
+	var out map[string]any
 	httpRequest(t, hc, http.MethodGet, "http://myuser:mypass@localhost:9997/v3/config/global/get", nil, &out)
 	require.Equal(t, true, out["api"])
 
@@ -202,7 +202,7 @@ func TestConfigGlobalPatch(t *testing.T) {
 	hc := &http.Client{Transport: tr}
 
 	httpRequest(t, hc, http.MethodPatch, "http://localhost:9997/v3/config/global/patch",
-		map[string]interface{}{
+		map[string]any{
 			"rtmp":            false,
 			"readTimeout":     "7s",
 			"protocols":       []string{"tcp"},
@@ -211,11 +211,11 @@ func TestConfigGlobalPatch(t *testing.T) {
 
 	time.Sleep(500 * time.Millisecond)
 
-	var out map[string]interface{}
+	var out map[string]any
 	httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/global/get", nil, &out)
 	require.Equal(t, false, out["rtmp"])
 	require.Equal(t, "7s", out["readTimeout"])
-	require.Equal(t, []interface{}{"tcp"}, out["protocols"])
+	require.Equal(t, []any{"tcp"}, out["protocols"])
 	require.Equal(t, float64(4096), out["readBufferCount"])
 }
 
@@ -234,7 +234,7 @@ func TestConfigGlobalPatchUnknownField(t *testing.T) { //nolint:dupl
 	require.NoError(t, err)
 	defer api.Close()
 
-	b := map[string]interface{}{
+	b := map[string]any{
 		"test": "asd",
 	}
 
@@ -276,7 +276,7 @@ func TestConfigPathDefaultsGet(t *testing.T) {
 	defer tr.CloseIdleConnections()
 	hc := &http.Client{Transport: tr}
 
-	var out map[string]interface{}
+	var out map[string]any
 	httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/pathdefaults/get", nil, &out)
 	require.Equal(t, "publisher", out["source"])
 }
@@ -301,13 +301,13 @@ func TestConfigPathDefaultsPatch(t *testing.T) {
 	hc := &http.Client{Transport: tr}
 
 	httpRequest(t, hc, http.MethodPatch, "http://localhost:9997/v3/config/pathdefaults/patch",
-		map[string]interface{}{
+		map[string]any{
 			"recordFormat": "fmp4",
 		}, nil)
 
 	time.Sleep(500 * time.Millisecond)
 
-	var out map[string]interface{}
+	var out map[string]any
 	httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/pathdefaults/get", nil, &out)
 	require.Equal(t, "fmp4", out["recordFormat"])
 }
@@ -334,7 +334,7 @@ func TestConfigPathsList(t *testing.T) {
 	require.NoError(t, err)
 	defer api.Close()
 
-	type pathConfig map[string]interface{}
+	type pathConfig map[string]any
 
 	type listRes struct {
 		ItemCount int          `json:"itemCount"`
@@ -381,7 +381,7 @@ func TestConfigPathsGet(t *testing.T) {
 	defer tr.CloseIdleConnections()
 	hc := &http.Client{Transport: tr}
 
-	var out map[string]interface{}
+	var out map[string]any
 	httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/paths/get/my/path", nil, &out)
 	require.Equal(t, "my/path", out["name"])
 	require.Equal(t, "myuser", out["readUser"])
@@ -407,14 +407,14 @@ func TestConfigPathsAdd(t *testing.T) {
 	hc := &http.Client{Transport: tr}
 
 	httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/config/paths/add/my/path",
-		map[string]interface{}{
+		map[string]any{
 			"source":                   "rtsp://127.0.0.1:9999/mypath",
 			"sourceOnDemand":           true,
 			"disablePublisherOverride": true, // test setting a deprecated parameter
 			"rpiCameraVFlip":           true,
 		}, nil)
 
-	var out map[string]interface{}
+	var out map[string]any
 	httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/paths/get/my/path", nil, &out)
 	require.Equal(t, "rtsp://127.0.0.1:9999/mypath", out["source"])
 	require.Equal(t, true, out["sourceOnDemand"])
@@ -437,7 +437,7 @@ func TestConfigPathsAddUnknownField(t *testing.T) { //nolint:dupl
 	require.NoError(t, err)
 	defer api.Close()
 
-	b := map[string]interface{}{
+	b := map[string]any{
 		"test": "asd",
 	}
 
@@ -480,7 +480,7 @@ func TestConfigPathsPatch(t *testing.T) { //nolint:dupl
 	hc := &http.Client{Transport: tr}
 
 	httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/config/paths/add/my/path",
-		map[string]interface{}{
+		map[string]any{
 			"source":                   "rtsp://127.0.0.1:9999/mypath",
 			"sourceOnDemand":           true,
 			"disablePublisherOverride": true, // test setting a deprecated parameter
@@ -488,12 +488,12 @@ func TestConfigPathsPatch(t *testing.T) { //nolint:dupl
 		}, nil)
 
 	httpRequest(t, hc, http.MethodPatch, "http://localhost:9997/v3/config/paths/patch/my/path",
-		map[string]interface{}{
+		map[string]any{
 			"source":         "rtsp://127.0.0.1:9998/mypath",
 			"sourceOnDemand": true,
 		}, nil)
 
-	var out map[string]interface{}
+	var out map[string]any
 	httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/paths/get/my/path", nil, &out)
 	require.Equal(t, "rtsp://127.0.0.1:9998/mypath", out["source"])
 	require.Equal(t, true, out["sourceOnDemand"])
@@ -521,7 +521,7 @@ func TestConfigPathsReplace(t *testing.T) { //nolint:dupl
 	hc := &http.Client{Transport: tr}
 
 	httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/config/paths/add/my/path",
-		map[string]interface{}{
+		map[string]any{
 			"source":                   "rtsp://127.0.0.1:9999/mypath",
 			"sourceOnDemand":           true,
 			"disablePublisherOverride": true, // test setting a deprecated parameter
@@ -529,12 +529,12 @@ func TestConfigPathsReplace(t *testing.T) { //nolint:dupl
 		}, nil)
 
 	httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/config/paths/replace/my/path",
-		map[string]interface{}{
+		map[string]any{
 			"source":         "rtsp://127.0.0.1:9998/mypath",
 			"sourceOnDemand": true,
 		}, nil)
 
-	var out map[string]interface{}
+	var out map[string]any
 	httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/paths/get/my/path", nil, &out)
 	require.Equal(t, "rtsp://127.0.0.1:9998/mypath", out["source"])
 	require.Equal(t, true, out["sourceOnDemand"])
@@ -562,12 +562,12 @@ func TestConfigPathsReplaceNonExisting(t *testing.T) { //nolint:dupl
 	hc := &http.Client{Transport: tr}
 
 	httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/config/paths/replace/my/path",
-		map[string]interface{}{
+		map[string]any{
 			"source":         "rtsp://127.0.0.1:9998/mypath",
 			"sourceOnDemand": true,
 		}, nil)
 
-	var out map[string]interface{}
+	var out map[string]any
 	httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/paths/get/my/path", nil, &out)
 	require.Equal(t, "rtsp://127.0.0.1:9998/mypath", out["source"])
 	require.Equal(t, true, out["sourceOnDemand"])
@@ -595,7 +595,7 @@ func TestConfigPathsDelete(t *testing.T) {
 	hc := &http.Client{Transport: tr}
 
 	httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/config/paths/add/my/path",
-		map[string]interface{}{
+		map[string]any{
 			"source":         "rtsp://127.0.0.1:9999/mypath",
 			"sourceOnDemand": true,
 		}, nil)
@@ -655,27 +655,27 @@ func TestRecordingsList(t *testing.T) {
 	defer tr.CloseIdleConnections()
 	hc := &http.Client{Transport: tr}
 
-	var out interface{}
+	var out any
 	httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/recordings/list", nil, &out)
-	require.Equal(t, map[string]interface{}{
+	require.Equal(t, map[string]any{
 		"itemCount": float64(2),
 		"pageCount": float64(1),
-		"items": []interface{}{
-			map[string]interface{}{
+		"items": []any{
+			map[string]any{
 				"name": "mypath1",
-				"segments": []interface{}{
-					map[string]interface{}{
+				"segments": []any{
+					map[string]any{
 						"start": time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
 					},
-					map[string]interface{}{
+					map[string]any{
 						"start": time.Date(2009, 11, 7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano),
 					},
 				},
 			},
-			map[string]interface{}{
+			map[string]any{
 				"name": "mypath2",
-				"segments": []interface{}{
-					map[string]interface{}{
+				"segments": []any{
+					map[string]any{
 						"start": time.Date(2009, 11, 7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano),
 					},
 				},
@@ -719,15 +719,15 @@ func TestRecordingsGet(t *testing.T) {
 	defer tr.CloseIdleConnections()
 	hc := &http.Client{Transport: tr}
 
-	var out interface{}
+	var out any
 	httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/recordings/get/mypath1", nil, &out)
-	require.Equal(t, map[string]interface{}{
+	require.Equal(t, map[string]any{
 		"name": "mypath1",
-		"segments": []interface{}{
-			map[string]interface{}{
+		"segments": []any{
+			map[string]any{
 				"start": time.Date(2008, 11, 7, 11, 22, 0, 0, time.Local).Format(time.RFC3339Nano),
 			},
-			map[string]interface{}{
+			map[string]any{
 				"start": time.Date(2009, 11, 7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano),
 			},
 		},
@@ -840,7 +840,7 @@ func TestAuthError(t *testing.T) {
 			},
 		},
 		Parent: &testParent{
-			log: func(l logger.Level, s string, i ...interface{}) {
+			log: func(l logger.Level, s string, i ...any) {
 				if l == logger.Info {
 					if n == 1 {
 						require.Regexp(t, "failed to authenticate: auth error$", fmt.Sprintf(s, i...))
diff --git a/internal/api/paginate_test.go b/internal/api/paginate_test.go
index cd5daea8..6ffcd3c1 100644
--- a/internal/api/paginate_test.go
+++ b/internal/api/paginate_test.go
@@ -9,7 +9,7 @@ import (
 func TestPaginate(t *testing.T) {
 	func() {
 		items := make([]int, 5)
-		for i := 0; i < 5; i++ {
+		for i := range 5 {
 			items[i] = i
 		}
 
@@ -21,7 +21,7 @@ func TestPaginate(t *testing.T) {
 
 	func() {
 		items := make([]int, 5)
-		for i := 0; i < 5; i++ {
+		for i := range 5 {
 			items[i] = i
 		}
 
@@ -33,7 +33,7 @@ func TestPaginate(t *testing.T) {
 
 	func() {
 		items := make([]int, 6)
-		for i := 0; i < 6; i++ {
+		for i := range 6 {
 			items[i] = i
 		}
 
@@ -56,7 +56,7 @@ func TestPaginate(t *testing.T) {
 func FuzzPaginate(f *testing.F) {
 	f.Fuzz(func(_ *testing.T, str1 string, str2 string) {
 		items := make([]int, 6)
-		for i := 0; i < 6; i++ {
+		for i := range 6 {
 			items[i] = i
 		}
 
diff --git a/internal/auth/manager_test.go b/internal/auth/manager_test.go
index 3c7b52b4..160db708 100644
--- a/internal/auth/manager_test.go
+++ b/internal/auth/manager_test.go
@@ -539,7 +539,7 @@ func TestAuthJWTRefresh(t *testing.T) {
 		JWTClaimKey: "my_permission_key",
 	}
 
-	for i := 0; i < 2; i++ {
+	for range 2 {
 		key, err = rsa.GenerateKey(rand.Reader, 1024)
 		require.NoError(t, err)
 
diff --git a/internal/codecprocessor/h264_test.go b/internal/codecprocessor/h264_test.go
index 6c44f757..6f124fb6 100644
--- a/internal/codecprocessor/h264_test.go
+++ b/internal/codecprocessor/h264_test.go
@@ -15,15 +15,15 @@ import (
 )
 
 type testLogger struct {
-	cb func(level logger.Level, format string, args ...interface{})
+	cb func(level logger.Level, format string, args ...any)
 }
 
-func (l *testLogger) Log(level logger.Level, format string, args ...interface{}) {
+func (l *testLogger) Log(level logger.Level, format string, args ...any) {
 	l.cb(level, format, args...)
 }
 
 // Logger returns a dummy logger.
-func Logger(cb func(logger.Level, string, ...interface{})) logger.Writer {
+func Logger(cb func(logger.Level, string, ...any)) logger.Writer {
 	return &testLogger{cb: cb}
 }
 
@@ -202,7 +202,7 @@ func TestH264RTPOversized(t *testing.T) {
 	logged := false
 
 	p, err := New(1460, forma, false,
-		Logger(func(_ logger.Level, s string, i ...interface{}) {
+		Logger(func(_ logger.Level, s string, i ...any) {
 			require.Equal(t, "RTP packets are too big, remuxing them into smaller ones", fmt.Sprintf(s, i...))
 			logged = true
 		}))
diff --git a/internal/codecprocessor/h265_test.go b/internal/codecprocessor/h265_test.go
index bab041fa..67767ace 100644
--- a/internal/codecprocessor/h265_test.go
+++ b/internal/codecprocessor/h265_test.go
@@ -202,7 +202,7 @@ func TestH265RTPOversized(t *testing.T) {
 	logged := false
 
 	p, err := New(1460, forma, false,
-		Logger(func(_ logger.Level, s string, i ...interface{}) {
+		Logger(func(_ logger.Level, s string, i ...any) {
 			require.Equal(t, "RTP packets are too big, remuxing them into smaller ones", fmt.Sprintf(s, i...))
 			logged = true
 		}))
diff --git a/internal/core/api_test.go b/internal/core/api_test.go
index d93242b9..ff42bbca 100644
--- a/internal/core/api_test.go
+++ b/internal/core/api_test.go
@@ -34,7 +34,7 @@ func checkClose(t *testing.T, closeFunc func() error) {
 	require.NoError(t, closeFunc())
 }
 
-func httpRequest(t *testing.T, hc *http.Client, method string, ur string, in interface{}, out interface{}) {
+func httpRequest(t *testing.T, hc *http.Client, method string, ur string, in any, out any) {
 	buf := func() io.Reader {
 		if in == nil {
 			return nil
@@ -66,10 +66,10 @@ func httpRequest(t *testing.T, hc *http.Client, method string, ur string, in int
 }
 
 func checkError(t *testing.T, msg string, body io.Reader) {
-	var resErr map[string]interface{}
+	var resErr map[string]any
 	err := json.NewDecoder(body).Decode(&resErr)
 	require.NoError(t, err)
-	require.Equal(t, map[string]interface{}{"error": msg}, resErr)
+	require.Equal(t, map[string]any{"error": msg}, resErr)
 }
 
 func TestAPIPathsList(t *testing.T) {
@@ -462,7 +462,7 @@ func TestAPIProtocolListGet(t *testing.T) {
 				go func() {
 					time.Sleep(500 * time.Millisecond)
 
-					for i := 0; i < 3; i++ {
+					for i := range 3 {
 						/*source.WritePacketRTP(medi, &rtp.Packet{
 							Header: rtp.Header{
 								Version:        2,
@@ -604,40 +604,40 @@ func TestAPIProtocolListGet(t *testing.T) {
 				pa = "srtconns"
 			}
 
-			var out1 interface{}
+			var out1 any
 			httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/"+pa+"/list", nil, &out1)
 
 			switch ca {
 			case "rtsp conns":
-				require.Equal(t, map[string]interface{}{
+				require.Equal(t, map[string]any{
 					"pageCount": float64(1),
 					"itemCount": float64(1),
-					"items": []interface{}{
-						map[string]interface{}{
-							"bytesReceived": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesReceived"],
-							"bytesSent":     out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"],
-							"created":       out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"],
-							"id":            out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"],
-							"remoteAddr":    out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"],
-							"session":       out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["session"],
+					"items": []any{
+						map[string]any{
+							"bytesReceived": out1.(map[string]any)["items"].([]any)[0].(map[string]any)["bytesReceived"],
+							"bytesSent":     out1.(map[string]any)["items"].([]any)[0].(map[string]any)["bytesSent"],
+							"created":       out1.(map[string]any)["items"].([]any)[0].(map[string]any)["created"],
+							"id":            out1.(map[string]any)["items"].([]any)[0].(map[string]any)["id"],
+							"remoteAddr":    out1.(map[string]any)["items"].([]any)[0].(map[string]any)["remoteAddr"],
+							"session":       out1.(map[string]any)["items"].([]any)[0].(map[string]any)["session"],
 							"tunnel":        "none",
 						},
 					},
 				}, out1)
 
 			case "rtsp sessions":
-				require.Equal(t, map[string]interface{}{
+				require.Equal(t, map[string]any{
 					"pageCount": float64(1),
 					"itemCount": float64(1),
-					"items": []interface{}{
-						map[string]interface{}{
+					"items": []any{
+						map[string]any{
 							"bytesReceived":       float64(0),
-							"bytesSent":           out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"],
-							"created":             out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"],
-							"id":                  out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"],
+							"bytesSent":           out1.(map[string]any)["items"].([]any)[0].(map[string]any)["bytesSent"],
+							"created":             out1.(map[string]any)["items"].([]any)[0].(map[string]any)["created"],
+							"id":                  out1.(map[string]any)["items"].([]any)[0].(map[string]any)["id"],
 							"path":                "mypath",
 							"query":               "key=val",
-							"remoteAddr":          out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"],
+							"remoteAddr":          out1.(map[string]any)["items"].([]any)[0].(map[string]any)["remoteAddr"],
 							"state":               "publish",
 							"transport":           "UDP",
 							"profile":             "AVP",
@@ -654,35 +654,35 @@ func TestAPIProtocolListGet(t *testing.T) {
 				}, out1)
 
 			case "rtsps conns":
-				require.Equal(t, map[string]interface{}{
+				require.Equal(t, map[string]any{
 					"pageCount": float64(1),
 					"itemCount": float64(1),
-					"items": []interface{}{
-						map[string]interface{}{
-							"bytesReceived": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesReceived"],
-							"bytesSent":     out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"],
-							"created":       out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"],
-							"id":            out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"],
-							"remoteAddr":    out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"],
-							"session":       out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["session"],
+					"items": []any{
+						map[string]any{
+							"bytesReceived": out1.(map[string]any)["items"].([]any)[0].(map[string]any)["bytesReceived"],
+							"bytesSent":     out1.(map[string]any)["items"].([]any)[0].(map[string]any)["bytesSent"],
+							"created":       out1.(map[string]any)["items"].([]any)[0].(map[string]any)["created"],
+							"id":            out1.(map[string]any)["items"].([]any)[0].(map[string]any)["id"],
+							"remoteAddr":    out1.(map[string]any)["items"].([]any)[0].(map[string]any)["remoteAddr"],
+							"session":       out1.(map[string]any)["items"].([]any)[0].(map[string]any)["session"],
 							"tunnel":        "none",
 						},
 					},
 				}, out1)
 
 			case "rtsps sessions":
-				require.Equal(t, map[string]interface{}{
+				require.Equal(t, map[string]any{
 					"pageCount": float64(1),
 					"itemCount": float64(1),
-					"items": []interface{}{
-						map[string]interface{}{
+					"items": []any{
+						map[string]any{
 							"bytesReceived":       float64(0),
-							"bytesSent":           out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"],
-							"created":             out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"],
-							"id":                  out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"],
+							"bytesSent":           out1.(map[string]any)["items"].([]any)[0].(map[string]any)["bytesSent"],
+							"created":             out1.(map[string]any)["items"].([]any)[0].(map[string]any)["created"],
+							"id":                  out1.(map[string]any)["items"].([]any)[0].(map[string]any)["id"],
 							"path":                "mypath",
 							"query":               "key=val",
-							"remoteAddr":          out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"],
+							"remoteAddr":          out1.(map[string]any)["items"].([]any)[0].(map[string]any)["remoteAddr"],
 							"state":               "publish",
 							"transport":           "UDP",
 							"profile":             "SAVP",
@@ -699,71 +699,71 @@ func TestAPIProtocolListGet(t *testing.T) {
 				}, out1)
 
 			case "rtmp":
-				require.Equal(t, map[string]interface{}{
+				require.Equal(t, map[string]any{
 					"pageCount": float64(1),
 					"itemCount": float64(1),
-					"items": []interface{}{
-						map[string]interface{}{
-							"bytesReceived": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesReceived"],
-							"bytesSent":     out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"],
-							"created":       out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"],
-							"id":            out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"],
+					"items": []any{
+						map[string]any{
+							"bytesReceived": out1.(map[string]any)["items"].([]any)[0].(map[string]any)["bytesReceived"],
+							"bytesSent":     out1.(map[string]any)["items"].([]any)[0].(map[string]any)["bytesSent"],
+							"created":       out1.(map[string]any)["items"].([]any)[0].(map[string]any)["created"],
+							"id":            out1.(map[string]any)["items"].([]any)[0].(map[string]any)["id"],
 							"path":          "mypath",
 							"query":         "key=val",
-							"remoteAddr":    out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"],
+							"remoteAddr":    out1.(map[string]any)["items"].([]any)[0].(map[string]any)["remoteAddr"],
 							"state":         "publish",
 						},
 					},
 				}, out1)
 
 			case "rtmps":
-				require.Equal(t, map[string]interface{}{
+				require.Equal(t, map[string]any{
 					"pageCount": float64(1),
 					"itemCount": float64(1),
-					"items": []interface{}{
-						map[string]interface{}{
-							"bytesReceived": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesReceived"],
-							"bytesSent":     out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"],
-							"created":       out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"],
-							"id":            out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"],
+					"items": []any{
+						map[string]any{
+							"bytesReceived": out1.(map[string]any)["items"].([]any)[0].(map[string]any)["bytesReceived"],
+							"bytesSent":     out1.(map[string]any)["items"].([]any)[0].(map[string]any)["bytesSent"],
+							"created":       out1.(map[string]any)["items"].([]any)[0].(map[string]any)["created"],
+							"id":            out1.(map[string]any)["items"].([]any)[0].(map[string]any)["id"],
 							"path":          "mypath",
 							"query":         "key=val",
-							"remoteAddr":    out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"],
+							"remoteAddr":    out1.(map[string]any)["items"].([]any)[0].(map[string]any)["remoteAddr"],
 							"state":         "publish",
 						},
 					},
 				}, out1)
 
 			case "hls":
-				require.Equal(t, map[string]interface{}{
+				require.Equal(t, map[string]any{
 					"itemCount": float64(1),
 					"pageCount": float64(1),
-					"items": []interface{}{
-						map[string]interface{}{
-							"bytesSent":   out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"],
-							"created":     out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"],
-							"lastRequest": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["lastRequest"],
+					"items": []any{
+						map[string]any{
+							"bytesSent":   out1.(map[string]any)["items"].([]any)[0].(map[string]any)["bytesSent"],
+							"created":     out1.(map[string]any)["items"].([]any)[0].(map[string]any)["created"],
+							"lastRequest": out1.(map[string]any)["items"].([]any)[0].(map[string]any)["lastRequest"],
 							"path":        "mypath",
 						},
 					},
 				}, out1)
 
 			case "webrtc":
-				require.Equal(t, map[string]interface{}{
+				require.Equal(t, map[string]any{
 					"itemCount": float64(1),
 					"pageCount": float64(1),
-					"items": []interface{}{
-						map[string]interface{}{
-							"bytesReceived":             out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesReceived"],
-							"bytesSent":                 out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"],
-							"created":                   out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"],
-							"id":                        out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"],
-							"localCandidate":            out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["localCandidate"],
+					"items": []any{
+						map[string]any{
+							"bytesReceived":             out1.(map[string]any)["items"].([]any)[0].(map[string]any)["bytesReceived"],
+							"bytesSent":                 out1.(map[string]any)["items"].([]any)[0].(map[string]any)["bytesSent"],
+							"created":                   out1.(map[string]any)["items"].([]any)[0].(map[string]any)["created"],
+							"id":                        out1.(map[string]any)["items"].([]any)[0].(map[string]any)["id"],
+							"localCandidate":            out1.(map[string]any)["items"].([]any)[0].(map[string]any)["localCandidate"],
 							"path":                      "mypath",
 							"peerConnectionEstablished": true,
 							"query":                     "key=val",
-							"remoteAddr":                out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"],
-							"remoteCandidate":           out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteCandidate"],
+							"remoteAddr":                out1.(map[string]any)["items"].([]any)[0].(map[string]any)["remoteAddr"],
+							"remoteCandidate":           out1.(map[string]any)["items"].([]any)[0].(map[string]any)["remoteCandidate"],
 							"state":                     "read",
 							"rtcpPacketsReceived":       float64(0),
 							"rtcpPacketsSent":           float64(2),
@@ -776,11 +776,11 @@ func TestAPIProtocolListGet(t *testing.T) {
 				}, out1)
 
 			case "srt":
-				require.Equal(t, map[string]interface{}{
+				require.Equal(t, map[string]any{
 					"itemCount": float64(1),
 					"pageCount": float64(1),
-					"items": []interface{}{
-						map[string]interface{}{
+					"items": []any{
+						map[string]any{
 							"byteMSS":                       float64(1500),
 							"bytesAvailReceiveBuf":          float64(0),
 							"bytesAvailSendBuf":             float64(0),
@@ -797,13 +797,13 @@ func TestAPIProtocolListGet(t *testing.T) {
 							"bytesSendDrop":                 float64(0),
 							"bytesSent":                     float64(0),
 							"bytesSentUnique":               float64(0),
-							"created":                       out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"],
-							"id":                            out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"],
+							"created":                       out1.(map[string]any)["items"].([]any)[0].(map[string]any)["created"],
+							"id":                            out1.(map[string]any)["items"].([]any)[0].(map[string]any)["id"],
 							"mbpsLinkCapacity":              float64(0),
 							"mbpsMaxBW":                     float64(-1),
 							"mbpsReceiveRate":               float64(0),
 							"mbpsSendRate":                  float64(0),
-							"msRTT":                         out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["msRTT"],
+							"msRTT":                         out1.(map[string]any)["items"].([]any)[0].(map[string]any)["msRTT"],
 							"msReceiveBuf":                  float64(0),
 							"msReceiveTsbPdDelay":           float64(120),
 							"msSendBuf":                     float64(0),
@@ -812,7 +812,7 @@ func TestAPIProtocolListGet(t *testing.T) {
 							"packetsFlowWindow":             float64(25600),
 							"packetsReceiveBuf":             float64(0),
 							"packetsReceived":               float64(1),
-							"packetsReceivedACK":            out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["packetsReceivedACK"],
+							"packetsReceivedACK":            out1.(map[string]any)["items"].([]any)[0].(map[string]any)["packetsReceivedACK"],
 							"packetsReceivedAvgBelatedTime": float64(0),
 							"packetsReceivedBelated":        float64(0),
 							"packetsReceivedDrop":           float64(0),
@@ -830,13 +830,13 @@ func TestAPIProtocolListGet(t *testing.T) {
 							"packetsSendLoss":               float64(0),
 							"packetsSendLossRate":           float64(0),
 							"packetsSent":                   float64(0),
-							"packetsSentACK":                out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["packetsSentACK"],
+							"packetsSentACK":                out1.(map[string]any)["items"].([]any)[0].(map[string]any)["packetsSentACK"],
 							"packetsSentKM":                 float64(0),
 							"packetsSentNAK":                float64(0),
 							"packetsSentUnique":             float64(0),
 							"path":                          "mypath",
 							"query":                         "key=val",
-							"remoteAddr":                    out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"],
+							"remoteAddr":                    out1.(map[string]any)["items"].([]any)[0].(map[string]any)["remoteAddr"],
 							"state":                         "publish",
 							"usPacketsSendPeriod":           float64(10.967254638671875),
 							"usSndDuration":                 float64(0),
@@ -845,19 +845,19 @@ func TestAPIProtocolListGet(t *testing.T) {
 				}, out1)
 			}
 
-			var out2 interface{}
+			var out2 any
 
 			if ca == "hls" {
 				httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/"+pa+"/get/"+
-					out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["path"].(string),
+					out1.(map[string]any)["items"].([]any)[0].(map[string]any)["path"].(string),
 					nil, &out2)
 			} else {
 				httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/"+pa+"/get/"+
-					out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"].(string),
+					out1.(map[string]any)["items"].([]any)[0].(map[string]any)["id"].(string),
 					nil, &out2)
 			}
 
-			require.Equal(t, out1.(map[string]interface{})["items"].([]interface{})[0], out2)
+			require.Equal(t, out1.(map[string]any)["items"].([]any)[0], out2)
 		})
 	}
 }
diff --git a/internal/core/metrics_test.go b/internal/core/metrics_test.go
index 6c73ef10..d4e44ebd 100644
--- a/internal/core/metrics_test.go
+++ b/internal/core/metrics_test.go
@@ -482,7 +482,7 @@ webrtc_sessions_rtcp_packets_sent 0
 	})
 
 	t.Run("servers disabled", func(t *testing.T) {
-		httpRequest(t, hc, http.MethodPatch, "http://localhost:9997/v3/config/global/patch", map[string]interface{}{
+		httpRequest(t, hc, http.MethodPatch, "http://localhost:9997/v3/config/global/patch", map[string]any{
 			"rtsp":   false,
 			"rtmp":   false,
 			"srt":    false,
diff --git a/internal/core/path_manager_test.go b/internal/core/path_manager_test.go
index f3bed21b..edf575c6 100644
--- a/internal/core/path_manager_test.go
+++ b/internal/core/path_manager_test.go
@@ -134,13 +134,13 @@ func TestPathConfigurationHotReload(t *testing.T) {
 	require.Equal(t, "all", pathData.ConfName)
 
 	// Check the current configuration via API
-	var allConfig map[string]interface{}
+	var allConfig map[string]any
 	httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/paths/get/all", nil, &allConfig)
 	require.Equal(t, false, allConfig["record"]) // Should be false from "all" config
 
 	// Add a new specific configuration for "undefined_stream" with record enabled
 	httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/config/paths/add/undefined_stream",
-		map[string]interface{}{
+		map[string]any{
 			"record": true,
 		}, nil)
 
@@ -154,7 +154,7 @@ func TestPathConfigurationHotReload(t *testing.T) {
 	require.Equal(t, "undefined_stream", pathData.ConfName) // Should now use the specific config
 
 	// Check the new configuration via API
-	var newConfig map[string]interface{}
+	var newConfig map[string]any
 	httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/paths/get/undefined_stream", nil, &newConfig)
 	require.Equal(t, true, newConfig["record"]) // Should be true from new config
 
diff --git a/internal/core/path_test.go b/internal/core/path_test.go
index 7a93bc97..42deecc1 100644
--- a/internal/core/path_test.go
+++ b/internal/core/path_test.go
@@ -487,7 +487,7 @@ func TestPathRunOnRead(t *testing.T) {
 					defer conn.Close()
 
 					go func() {
-						for i := uint16(0); i < 3; i++ {
+						for i := range uint16(3) {
 							err2 := source.WritePacketRTP(media0, &rtp.Packet{
 								Header: rtp.Header{
 									Version:        2,
@@ -622,7 +622,7 @@ func TestPathRunOnRecordSegment(t *testing.T) {
 		require.NoError(t, err)
 		defer source.Close()
 
-		for i := 0; i < 4; i++ {
+		for i := range 4 {
 			err = source.WritePacketRTP(media0, &rtp.Packet{
 				Header: rtp.Header{
 					Version:        2,
@@ -672,7 +672,7 @@ func TestPathMaxReaders(t *testing.T) {
 	require.NoError(t, err)
 	defer source.Close()
 
-	for i := 0; i < 2; i++ {
+	for i := range 2 {
 		var u *base.URL
 		u, err = base.ParseURL("rtsp://127.0.0.1:8554/mystream")
 		require.NoError(t, err)
@@ -723,7 +723,7 @@ func TestPathRecord(t *testing.T) {
 	require.NoError(t, err)
 	defer source.Close()
 
-	for i := 0; i < 4; i++ {
+	for i := range 4 {
 		err = source.WritePacketRTP(media0, &rtp.Packet{
 			Header: rtp.Header{
 				Version:        2,
@@ -748,13 +748,13 @@ func TestPathRecord(t *testing.T) {
 	defer tr.CloseIdleConnections()
 	hc := &http.Client{Transport: tr}
 
-	httpRequest(t, hc, http.MethodPatch, "http://localhost:9997/v3/config/paths/patch/all_others", map[string]interface{}{
+	httpRequest(t, hc, http.MethodPatch, "http://localhost:9997/v3/config/paths/patch/all_others", map[string]any{
 		"record": false,
 	}, nil)
 
 	time.Sleep(500 * time.Millisecond)
 
-	httpRequest(t, hc, http.MethodPatch, "http://localhost:9997/v3/config/paths/patch/all_others", map[string]interface{}{
+	httpRequest(t, hc, http.MethodPatch, "http://localhost:9997/v3/config/paths/patch/all_others", map[string]any{
 		"record": true,
 	}, nil)
 
diff --git a/internal/metrics/metrics_test.go b/internal/metrics/metrics_test.go
index 71dc3e7d..45d9d4e4 100644
--- a/internal/metrics/metrics_test.go
+++ b/internal/metrics/metrics_test.go
@@ -379,7 +379,7 @@ func TestAuthError(t *testing.T) {
 				return &auth.Error{Wrapped: fmt.Errorf("auth error")}
 			},
 		},
-		Parent: test.Logger(func(l logger.Level, s string, i ...interface{}) {
+		Parent: test.Logger(func(l logger.Level, s string, i ...any) {
 			if l == logger.Info {
 				if n == 1 {
 					require.Regexp(t, "failed to authenticate: auth error$", fmt.Sprintf(s, i...))
diff --git a/internal/playback/on_list_test.go b/internal/playback/on_list_test.go
index 96a7f47f..8f71750b 100644
--- a/internal/playback/on_list_test.go
+++ b/internal/playback/on_list_test.go
@@ -122,20 +122,20 @@ func TestOnList(t *testing.T) {
 
 			require.Equal(t, http.StatusOK, res.StatusCode)
 
-			var out interface{}
+			var out any
 			err = json.NewDecoder(res.Body).Decode(&out)
 			require.NoError(t, err)
 
 			switch ca {
 			case "unfiltered", "start before first":
-				require.Equal(t, []interface{}{
-					map[string]interface{}{
+				require.Equal(t, []any{
+					map[string]any{
 						"duration": float64(66),
 						"start":    time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
 						"url": "http://localhost:9996/get?duration=66&path=mypath&start=" +
 							url.QueryEscape(time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano)),
 					},
-					map[string]interface{}{
+					map[string]any{
 						"duration": float64(4),
 						"start":    time.Date(2009, 11, 7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano),
 						"url": "http://localhost:9996/get?duration=4&path=mypath&start=" +
@@ -144,14 +144,14 @@ func TestOnList(t *testing.T) {
 				}, out)
 
 			case "filtered":
-				require.Equal(t, []interface{}{
-					map[string]interface{}{
+				require.Equal(t, []any{
+					map[string]any{
 						"duration": float64(65),
 						"start":    time.Date(2008, 11, 7, 11, 22, 1, 500000000, time.Local).Format(time.RFC3339Nano),
 						"url": "http://localhost:9996/get?duration=65&path=mypath&start=" +
 							url.QueryEscape(time.Date(2008, 11, 7, 11, 22, 1, 500000000, time.Local).Format(time.RFC3339Nano)),
 					},
-					map[string]interface{}{
+					map[string]any{
 						"duration": float64(2),
 						"start":    time.Date(2009, 11, 7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano),
 						"url": "http://localhost:9996/get?duration=2&path=mypath&start=" +
@@ -160,8 +160,8 @@ func TestOnList(t *testing.T) {
 				}, out)
 
 			case "filtered and gap":
-				require.Equal(t, []interface{}{
-					map[string]interface{}{
+				require.Equal(t, []any{
+					map[string]any{
 						"duration": float64(4),
 						"start":    time.Date(2008, 11, 7, 11, 24, 2, 500000000, time.Local).Format(time.RFC3339Nano),
 						"url": "http://localhost:9996/get?duration=4&path=mypath&start=" +
@@ -170,14 +170,14 @@ func TestOnList(t *testing.T) {
 				}, out)
 
 			case "different init":
-				require.Equal(t, []interface{}{
-					map[string]interface{}{
+				require.Equal(t, []any{
+					map[string]any{
 						"duration": float64(62),
 						"start":    time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
 						"url": "http://localhost:9996/get?duration=62&path=mypath&start=" +
 							url.QueryEscape(time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano)),
 					},
-					map[string]interface{}{
+					map[string]any{
 						"duration": float64(1),
 						"start":    time.Date(2008, 11, 7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano),
 						"url": "http://localhost:9996/get?duration=1&path=mypath&start=" +
@@ -322,12 +322,12 @@ func TestOnListCachedDuration(t *testing.T) {
 
 	require.Equal(t, http.StatusOK, res.StatusCode)
 
-	var out interface{}
+	var out any
 	err = json.NewDecoder(res.Body).Decode(&out)
 	require.NoError(t, err)
 
-	require.Equal(t, []interface{}{
-		map[string]interface{}{
+	require.Equal(t, []any{
+		map[string]any{
 			"duration": float64(50),
 			"start":    time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
 			"url": "http://localhost:9996/get?duration=50&path=mypath&start=" +
diff --git a/internal/playback/segment_fmp4_test.go b/internal/playback/segment_fmp4_test.go
index 0a86b1e4..d221195b 100644
--- a/internal/playback/segment_fmp4_test.go
+++ b/internal/playback/segment_fmp4_test.go
@@ -59,7 +59,7 @@ func BenchmarkFMP4ReadHeader(b *testing.B) {
 	writeBenchInit(f)
 	f.Close()
 
-	for n := 0; n < b.N; n++ {
+	for b.Loop() {
 		func() {
 			f, err = os.Open(f.Name())
 			if err != nil {
diff --git a/internal/playback/server_test.go b/internal/playback/server_test.go
index 1c237eb0..aebcf119 100644
--- a/internal/playback/server_test.go
+++ b/internal/playback/server_test.go
@@ -67,7 +67,7 @@ func TestAuthError(t *testing.T) {
 				return &auth.Error{Wrapped: fmt.Errorf("auth error")}
 			},
 		},
-		Parent: test.Logger(func(l logger.Level, s string, i ...interface{}) {
+		Parent: test.Logger(func(l logger.Level, s string, i ...any) {
 			if l == logger.Info {
 				if n == 1 {
 					require.Regexp(t, "failed to authenticate: auth error$", fmt.Sprintf(s, i...))
diff --git a/internal/pprof/pprof_test.go b/internal/pprof/pprof_test.go
index 8e682b9b..1d61c9e6 100644
--- a/internal/pprof/pprof_test.go
+++ b/internal/pprof/pprof_test.go
@@ -110,7 +110,7 @@ func TestAuthError(t *testing.T) {
 				return &auth.Error{Wrapped: fmt.Errorf("auth error")}
 			},
 		},
-		Parent: test.Logger(func(l logger.Level, s string, i ...interface{}) {
+		Parent: test.Logger(func(l logger.Level, s string, i ...any) {
 			if l == logger.Info {
 				if n == 1 {
 					require.Regexp(t, "failed to authenticate: auth error$", fmt.Sprintf(s, i...))
diff --git a/internal/protocols/hls/from_stream_test.go b/internal/protocols/hls/from_stream_test.go
index 99c85c2d..acad4711 100644
--- a/internal/protocols/hls/from_stream_test.go
+++ b/internal/protocols/hls/from_stream_test.go
@@ -20,7 +20,7 @@ func TestFromStreamNoSupportedCodecs(t *testing.T) {
 	}}}
 
 	r := &stream.Reader{
-		Parent: test.Logger(func(logger.Level, string, ...interface{}) {
+		Parent: test.Logger(func(logger.Level, string, ...any) {
 			t.Error("should not happen")
 		}),
 	}
@@ -52,7 +52,7 @@ func TestFromStreamSkipUnsupportedTracks(t *testing.T) {
 	n := 0
 
 	r := &stream.Reader{
-		Parent: test.Logger(func(l logger.Level, format string, args ...interface{}) {
+		Parent: test.Logger(func(l logger.Level, format string, args ...any) {
 			require.Equal(t, logger.Warn, l)
 			switch n {
 			case 0:
diff --git a/internal/protocols/mpegts/from_stream_test.go b/internal/protocols/mpegts/from_stream_test.go
index 355c072a..f50eb92d 100644
--- a/internal/protocols/mpegts/from_stream_test.go
+++ b/internal/protocols/mpegts/from_stream_test.go
@@ -19,7 +19,7 @@ func TestFromStreamNoSupportedCodecs(t *testing.T) {
 	}}}
 
 	r := &stream.Reader{
-		Parent: test.Logger(func(logger.Level, string, ...interface{}) {
+		Parent: test.Logger(func(logger.Level, string, ...any) {
 			t.Error("should not happen")
 		}),
 	}
@@ -43,7 +43,7 @@ func TestFromStreamSkipUnsupportedTracks(t *testing.T) {
 	n := 0
 
 	r := &stream.Reader{
-		Parent: test.Logger(func(l logger.Level, format string, args ...interface{}) {
+		Parent: test.Logger(func(l logger.Level, format string, args ...any) {
 			require.Equal(t, logger.Warn, l)
 			if n == 0 {
 				require.Equal(t, "skipping track 2 (VP8)", fmt.Sprintf(format, args...))
diff --git a/internal/protocols/mpegts/to_stream_test.go b/internal/protocols/mpegts/to_stream_test.go
index 26c4ffa6..d8be208d 100644
--- a/internal/protocols/mpegts/to_stream_test.go
+++ b/internal/protocols/mpegts/to_stream_test.go
@@ -168,7 +168,7 @@ func TestToStreamNoSupportedCodecs(t *testing.T) {
 	err = r.Initialize()
 	require.NoError(t, err)
 
-	l := test.Logger(func(logger.Level, string, ...interface{}) {
+	l := test.Logger(func(logger.Level, string, ...any) {
 		t.Error("should not happen")
 	})
 	_, err = ToStream(r, nil, l)
@@ -202,7 +202,7 @@ func TestToStreamSkipUnsupportedTracks(t *testing.T) {
 
 	n := 0
 
-	l := test.Logger(func(l logger.Level, format string, args ...interface{}) {
+	l := test.Logger(func(l logger.Level, format string, args ...any) {
 		require.Equal(t, logger.Warn, l)
 		if n == 0 {
 			require.Equal(t, "skipping track 1 (unsupported codec)", fmt.Sprintf(format, args...))
diff --git a/internal/protocols/rtmp/from_stream_test.go b/internal/protocols/rtmp/from_stream_test.go
index 834821bb..e315b632 100644
--- a/internal/protocols/rtmp/from_stream_test.go
+++ b/internal/protocols/rtmp/from_stream_test.go
@@ -655,7 +655,7 @@ func TestFromStreamNoSupportedCodecs(t *testing.T) {
 	}}}
 
 	r := &stream.Reader{
-		Parent: test.Logger(func(logger.Level, string, ...interface{}) {
+		Parent: test.Logger(func(logger.Level, string, ...any) {
 			t.Error("should not happen")
 		}),
 	}
@@ -679,7 +679,7 @@ func TestFromStreamSkipUnsupportedTracks(t *testing.T) {
 	n := 0
 
 	r := &stream.Reader{
-		Parent: test.Logger(func(l logger.Level, format string, args ...interface{}) {
+		Parent: test.Logger(func(l logger.Level, format string, args ...any) {
 			require.Equal(t, logger.Warn, l)
 			if n == 0 {
 				require.Equal(t, "skipping track 1 (VP8)", fmt.Sprintf(format, args...))
diff --git a/internal/protocols/webrtc/from_stream_test.go b/internal/protocols/webrtc/from_stream_test.go
index c3a4a413..daabec66 100644
--- a/internal/protocols/webrtc/from_stream_test.go
+++ b/internal/protocols/webrtc/from_stream_test.go
@@ -22,7 +22,7 @@ func TestFromStreamNoSupportedCodecs(t *testing.T) {
 	}}}
 
 	r := &stream.Reader{
-		Parent: test.Logger(func(logger.Level, string, ...interface{}) {
+		Parent: test.Logger(func(logger.Level, string, ...any) {
 			t.Error("should not happen")
 		}),
 	}
@@ -46,7 +46,7 @@ func TestFromStreamSkipUnsupportedTracks(t *testing.T) {
 	n := 0
 
 	r := &stream.Reader{
-		Parent: test.Logger(func(l logger.Level, format string, args ...interface{}) {
+		Parent: test.Logger(func(l logger.Level, format string, args ...any) {
 			require.Equal(t, logger.Warn, l)
 			if n == 0 {
 				require.Equal(t, "skipping track 2 (M-JPEG)", fmt.Sprintf(format, args...))
diff --git a/internal/recorder/recorder_test.go b/internal/recorder/recorder_test.go
index fa0bfa62..adf516ca 100644
--- a/internal/recorder/recorder_test.go
+++ b/internal/recorder/recorder_test.go
@@ -73,7 +73,7 @@ func TestRecorder(t *testing.T) {
 	}}
 
 	writeToStream := func(strm *stream.Stream, startDTS int64, startNTP time.Time) {
-		for i := 0; i < 2; i++ {
+		for i := range 2 {
 			pts := startDTS + int64(i)*100*90000/1000
 			ntp := startNTP.Add(time.Duration(i*60) * time.Second)
 
@@ -207,7 +207,7 @@ func TestRecorder(t *testing.T) {
 				},
 			})
 
-			for i := 0; i < 2; i++ {
+			for range 2 {
 				<-segCreated
 				<-segDone
 			}
@@ -365,7 +365,7 @@ func TestRecorderFMP4NegativeInitialDTS(t *testing.T) {
 	}
 	w.Initialize()
 
-	for i := 0; i < 3; i++ {
+	for i := range 3 {
 		strm.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.Unit{
 			PTS: -50*90000/1000 + (int64(i) * 200 * 90000 / 1000),
 			NTP: time.Date(2008, 5, 20, 22, 15, 25, 0, time.UTC),
@@ -538,7 +538,7 @@ func TestRecorderSkipTracksPartial(t *testing.T) {
 
 			n := 0
 
-			l := test.Logger(func(l logger.Level, format string, args ...interface{}) {
+			l := test.Logger(func(l logger.Level, format string, args ...any) {
 				if n == 0 {
 					require.Equal(t, logger.Warn, l)
 					require.Equal(t, "[recorder] skipping track 2 (VP8)", fmt.Sprintf(format, args...))
@@ -600,7 +600,7 @@ func TestRecorderSkipTracksFull(t *testing.T) {
 
 			n := 0
 
-			l := test.Logger(func(l logger.Level, format string, args ...interface{}) {
+			l := test.Logger(func(l logger.Level, format string, args ...any) {
 				if n == 0 {
 					require.Equal(t, logger.Warn, l)
 					require.Equal(t, "[recorder] no supported tracks found, skipping recording", fmt.Sprintf(format, args...))
diff --git a/internal/recordstore/segment_test.go b/internal/recordstore/segment_test.go
index 6d1e18df..0e5768c4 100644
--- a/internal/recordstore/segment_test.go
+++ b/internal/recordstore/segment_test.go
@@ -11,6 +11,10 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
+func ptrOf[T any](v T) *T {
+	return &v
+}
+
 func TestFindAllPathsWithSegments(t *testing.T) {
 	dir, err := os.MkdirTemp("", "mediamtx-recordstore")
 	require.NoError(t, err)
@@ -93,14 +97,11 @@ func TestFindSegments(t *testing.T) {
 			case "no filtering":
 
 			case "filtering":
-				tmp1 := time.Date(2015, 5, 19, 22, 18, 25, 427000, time.Local)
-				start = &tmp1
-				tmp2 := start.Add(60 * time.Minute)
-				end = &tmp2
+				start = ptrOf(time.Date(2015, 5, 19, 22, 18, 25, 427000, time.Local))
+				end = ptrOf(start.Add(60 * time.Minute))
 
 			case "start before first":
-				tmp1 := time.Date(2014, 5, 19, 22, 18, 25, 427000, time.Local)
-				start = &tmp1
+				start = ptrOf(time.Date(2014, 5, 19, 22, 18, 25, 427000, time.Local))
 			}
 
 			segments, err := FindSegments(
diff --git a/internal/servers/hls/server_test.go b/internal/servers/hls/server_test.go
index 228498e1..356f0ace 100644
--- a/internal/servers/hls/server_test.go
+++ b/internal/servers/hls/server_test.go
@@ -285,7 +285,7 @@ func TestServerRead(t *testing.T) {
 
 				time.Sleep(100 * time.Millisecond)
 
-				for i := 0; i < 4; i++ {
+				for i := range 4 {
 					strm.WriteUnit(test.MediaH264, test.FormatH264, &unit.Unit{
 						NTP: time.Time{},
 						PTS: int64(i) * 90000,
@@ -526,7 +526,7 @@ func TestAuthError(t *testing.T) {
 				return nil, &auth.Error{Wrapped: fmt.Errorf("auth error")}
 			},
 		},
-		Parent: test.Logger(func(l logger.Level, s string, i ...interface{}) {
+		Parent: test.Logger(func(l logger.Level, s string, i ...any) {
 			if l == logger.Info {
 				if n == 1 {
 					require.Regexp(t, "failed to authenticate: auth error$", fmt.Sprintf(s, i...))
diff --git a/internal/servers/rtmp/server_test.go b/internal/servers/rtmp/server_test.go
index 05890c7d..7cf5a4e1 100644
--- a/internal/servers/rtmp/server_test.go
+++ b/internal/servers/rtmp/server_test.go
@@ -87,7 +87,7 @@ func TestServerPublish(t *testing.T) {
 			}
 
 			s := &Server{
-				Address:             "127.0.0.1:1935",
+				Address:             "127.0.0.1:1939",
 				ReadTimeout:         conf.Duration(10 * time.Second),
 				WriteTimeout:        conf.Duration(10 * time.Second),
 				IsTLS:               encrypt == "tls",
@@ -113,7 +113,7 @@ func TestServerPublish(t *testing.T) {
 				rawURL += "rtmp://"
 			}
 
-			rawURL += "127.0.0.1:1935/teststream?user=myuser&pass=mypass&param=value"
+			rawURL += "127.0.0.1:1939/teststream?user=myuser&pass=mypass&param=value"
 
 			u, err := url.Parse(rawURL)
 			require.NoError(t, err)
@@ -217,7 +217,7 @@ func TestServerRead(t *testing.T) {
 			}
 
 			s := &Server{
-				Address:             "127.0.0.1:1935",
+				Address:             "127.0.0.1:1939",
 				ReadTimeout:         conf.Duration(10 * time.Second),
 				WriteTimeout:        conf.Duration(10 * time.Second),
 				IsTLS:               encrypt == "tls",
@@ -243,7 +243,7 @@ func TestServerRead(t *testing.T) {
 				rawURL += "rtmp://"
 			}
 
-			rawURL += "127.0.0.1:1935/teststream?user=myuser&pass=mypass&param=value"
+			rawURL += "127.0.0.1:1939/teststream?user=myuser&pass=mypass&param=value"
 
 			u, err := url.Parse(rawURL)
 			require.NoError(t, err)
diff --git a/internal/servers/rtsp/server_test.go b/internal/servers/rtsp/server_test.go
index 8a23a7b7..2741b532 100644
--- a/internal/servers/rtsp/server_test.go
+++ b/internal/servers/rtsp/server_test.go
@@ -414,7 +414,7 @@ func TestAuthError(t *testing.T) {
 		WriteTimeout:   conf.Duration(10 * time.Second),
 		WriteQueueSize: 512,
 		PathManager:    pathManager,
-		Parent: test.Logger(func(l logger.Level, s string, i ...interface{}) {
+		Parent: test.Logger(func(l logger.Level, s string, i ...any) {
 			if l == logger.Info {
 				if atomic.AddInt64(n, 1) == 3 {
 					require.Regexp(t, "authentication failed: auth error$", fmt.Sprintf(s, i...))
diff --git a/internal/servers/webrtc/server_test.go b/internal/servers/webrtc/server_test.go
index aae15d2c..63b02f43 100644
--- a/internal/servers/webrtc/server_test.go
+++ b/internal/servers/webrtc/server_test.go
@@ -751,7 +751,7 @@ func TestAuthError(t *testing.T) {
 				return nil, &auth.Error{Wrapped: fmt.Errorf("auth error")}
 			},
 		},
-		Parent: test.Logger(func(l logger.Level, s string, i ...interface{}) {
+		Parent: test.Logger(func(l logger.Level, s string, i ...any) {
 			if l == logger.Info {
 				if n == 1 {
 					require.Regexp(t, "failed to authenticate: auth error$", fmt.Sprintf(s, i...))
diff --git a/internal/test/logger.go b/internal/test/logger.go
index 4dd3256b..2f8e322e 100644
--- a/internal/test/logger.go
+++ b/internal/test/logger.go
@@ -4,21 +4,21 @@ import "github.com/bluenviron/mediamtx/internal/logger"
 
 type nilLogger struct{}
 
-func (nilLogger) Log(_ logger.Level, _ string, _ ...interface{}) {
+func (nilLogger) Log(_ logger.Level, _ string, _ ...any) {
 }
 
 // NilLogger is a logger to /dev/null
 var NilLogger logger.Writer = &nilLogger{}
 
 type testLogger struct {
-	cb func(level logger.Level, format string, args ...interface{})
+	cb func(level logger.Level, format string, args ...any)
 }
 
-func (l *testLogger) Log(level logger.Level, format string, args ...interface{}) {
+func (l *testLogger) Log(level logger.Level, format string, args ...any) {
 	l.cb(level, format, args...)
 }
 
 // Logger returns a dummy logger.
-func Logger(cb func(logger.Level, string, ...interface{})) logger.Writer {
+func Logger(cb func(logger.Level, string, ...any)) logger.Writer {
 	return &testLogger{cb: cb}
 }
diff --git a/internal/test/static_source_parent.go b/internal/test/static_source_parent.go
index 6bed8468..d3b8c220 100644
--- a/internal/test/static_source_parent.go
+++ b/internal/test/static_source_parent.go
@@ -15,7 +15,7 @@ type StaticSourceParent struct {
 }
 
 // Log implements logger.Writer.
-func (*StaticSourceParent) Log(logger.Level, string, ...interface{}) {}
+func (*StaticSourceParent) Log(logger.Level, string, ...any) {}
 
 // Initialize initializes StaticSourceParent.
 func (p *StaticSourceParent) Initialize() {