├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── build.rs ├── examples ├── filepub.rs └── videopub.rs ├── src ├── lib.rs └── moqpublisher │ ├── imp.rs │ └── mod.rs ├── tests └── moqpublisher.rs └── work_plan.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *~ 3 | *.bk 4 | *.swp 5 | .DS_Store 6 | .vscode 7 | builddir 8 | .meson-subproject-wrap-hash.txt 9 | .aider* 10 | 11 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "android-tzdata" 31 | version = "0.1.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 34 | 35 | [[package]] 36 | name = "android_system_properties" 37 | version = "0.1.5" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 40 | dependencies = [ 41 | "libc", 42 | ] 43 | 44 | [[package]] 45 | name = "anstream" 46 | version = "0.6.18" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 49 | dependencies = [ 50 | "anstyle", 51 | "anstyle-parse", 52 | "anstyle-query", 53 | "anstyle-wincon", 54 | "colorchoice", 55 | "is_terminal_polyfill", 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle" 61 | version = "1.0.10" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 64 | 65 | [[package]] 66 | name = "anstyle-parse" 67 | version = "0.2.6" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 70 | dependencies = [ 71 | "utf8parse", 72 | ] 73 | 74 | [[package]] 75 | name = "anstyle-query" 76 | version = "1.1.2" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 79 | dependencies = [ 80 | "windows-sys 0.59.0", 81 | ] 82 | 83 | [[package]] 84 | name = "anstyle-wincon" 85 | version = "3.0.6" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 88 | dependencies = [ 89 | "anstyle", 90 | "windows-sys 0.59.0", 91 | ] 92 | 93 | [[package]] 94 | name = "anyhow" 95 | version = "1.0.95" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" 98 | dependencies = [ 99 | "backtrace", 100 | ] 101 | 102 | [[package]] 103 | name = "atomic_refcell" 104 | version = "0.1.13" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" 107 | 108 | [[package]] 109 | name = "autocfg" 110 | version = "1.4.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 113 | 114 | [[package]] 115 | name = "aws-lc-rs" 116 | version = "1.12.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "f409eb70b561706bf8abba8ca9c112729c481595893fd06a2dd9af8ed8441148" 119 | dependencies = [ 120 | "aws-lc-sys", 121 | "paste", 122 | "zeroize", 123 | ] 124 | 125 | [[package]] 126 | name = "aws-lc-sys" 127 | version = "0.24.1" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "923ded50f602b3007e5e63e3f094c479d9c8a9b42d7f4034e4afe456aa48bfd2" 130 | dependencies = [ 131 | "bindgen", 132 | "cc", 133 | "cmake", 134 | "dunce", 135 | "fs_extra", 136 | "paste", 137 | ] 138 | 139 | [[package]] 140 | name = "backtrace" 141 | version = "0.3.74" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 144 | dependencies = [ 145 | "addr2line", 146 | "cfg-if", 147 | "libc", 148 | "miniz_oxide", 149 | "object", 150 | "rustc-demangle", 151 | "windows-targets", 152 | ] 153 | 154 | [[package]] 155 | name = "bindgen" 156 | version = "0.69.5" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" 159 | dependencies = [ 160 | "bitflags", 161 | "cexpr", 162 | "clang-sys", 163 | "itertools 0.12.1", 164 | "lazy_static", 165 | "lazycell", 166 | "log", 167 | "prettyplease", 168 | "proc-macro2", 169 | "quote", 170 | "regex", 171 | "rustc-hash 1.1.0", 172 | "shlex", 173 | "syn", 174 | "which", 175 | ] 176 | 177 | [[package]] 178 | name = "bitflags" 179 | version = "2.6.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 182 | 183 | [[package]] 184 | name = "bumpalo" 185 | version = "3.16.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 188 | 189 | [[package]] 190 | name = "byteorder" 191 | version = "1.5.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 194 | 195 | [[package]] 196 | name = "bytes" 197 | version = "1.9.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" 200 | 201 | [[package]] 202 | name = "cc" 203 | version = "1.2.7" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" 206 | dependencies = [ 207 | "jobserver", 208 | "libc", 209 | "shlex", 210 | ] 211 | 212 | [[package]] 213 | name = "cesu8" 214 | version = "1.1.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 217 | 218 | [[package]] 219 | name = "cexpr" 220 | version = "0.6.0" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 223 | dependencies = [ 224 | "nom", 225 | ] 226 | 227 | [[package]] 228 | name = "cfg-expr" 229 | version = "0.17.2" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "8d4ba6e40bd1184518716a6e1a781bf9160e286d219ccdb8ab2612e74cfe4789" 232 | dependencies = [ 233 | "smallvec", 234 | "target-lexicon", 235 | ] 236 | 237 | [[package]] 238 | name = "cfg-if" 239 | version = "1.0.0" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 242 | 243 | [[package]] 244 | name = "cfg_aliases" 245 | version = "0.2.1" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 248 | 249 | [[package]] 250 | name = "chrono" 251 | version = "0.4.39" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" 254 | dependencies = [ 255 | "android-tzdata", 256 | "iana-time-zone", 257 | "num-traits", 258 | "windows-targets", 259 | ] 260 | 261 | [[package]] 262 | name = "clang-sys" 263 | version = "1.8.1" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 266 | dependencies = [ 267 | "glob", 268 | "libc", 269 | "libloading", 270 | ] 271 | 272 | [[package]] 273 | name = "clap" 274 | version = "4.5.23" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" 277 | dependencies = [ 278 | "clap_builder", 279 | "clap_derive", 280 | ] 281 | 282 | [[package]] 283 | name = "clap_builder" 284 | version = "4.5.23" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" 287 | dependencies = [ 288 | "anstream", 289 | "anstyle", 290 | "clap_lex", 291 | "strsim", 292 | ] 293 | 294 | [[package]] 295 | name = "clap_derive" 296 | version = "4.5.18" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 299 | dependencies = [ 300 | "heck", 301 | "proc-macro2", 302 | "quote", 303 | "syn", 304 | ] 305 | 306 | [[package]] 307 | name = "clap_lex" 308 | version = "0.7.4" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 311 | 312 | [[package]] 313 | name = "cmake" 314 | version = "0.1.52" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e" 317 | dependencies = [ 318 | "cc", 319 | ] 320 | 321 | [[package]] 322 | name = "colorchoice" 323 | version = "1.0.3" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 326 | 327 | [[package]] 328 | name = "combine" 329 | version = "4.6.7" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 332 | dependencies = [ 333 | "bytes", 334 | "memchr", 335 | ] 336 | 337 | [[package]] 338 | name = "core-foundation" 339 | version = "0.9.4" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 342 | dependencies = [ 343 | "core-foundation-sys", 344 | "libc", 345 | ] 346 | 347 | [[package]] 348 | name = "core-foundation-sys" 349 | version = "0.8.7" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 352 | 353 | [[package]] 354 | name = "ctrlc" 355 | version = "3.4.5" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" 358 | dependencies = [ 359 | "nix", 360 | "windows-sys 0.59.0", 361 | ] 362 | 363 | [[package]] 364 | name = "displaydoc" 365 | version = "0.2.5" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 368 | dependencies = [ 369 | "proc-macro2", 370 | "quote", 371 | "syn", 372 | ] 373 | 374 | [[package]] 375 | name = "dunce" 376 | version = "1.0.5" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 379 | 380 | [[package]] 381 | name = "either" 382 | version = "1.13.0" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 385 | 386 | [[package]] 387 | name = "env_filter" 388 | version = "0.1.3" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 391 | dependencies = [ 392 | "log", 393 | "regex", 394 | ] 395 | 396 | [[package]] 397 | name = "env_logger" 398 | version = "0.11.6" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" 401 | dependencies = [ 402 | "anstream", 403 | "anstyle", 404 | "env_filter", 405 | "humantime", 406 | "log", 407 | ] 408 | 409 | [[package]] 410 | name = "equivalent" 411 | version = "1.0.1" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 414 | 415 | [[package]] 416 | name = "errno" 417 | version = "0.3.10" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 420 | dependencies = [ 421 | "libc", 422 | "windows-sys 0.59.0", 423 | ] 424 | 425 | [[package]] 426 | name = "fnv" 427 | version = "1.0.7" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 430 | 431 | [[package]] 432 | name = "form_urlencoded" 433 | version = "1.2.1" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 436 | dependencies = [ 437 | "percent-encoding", 438 | ] 439 | 440 | [[package]] 441 | name = "fs_extra" 442 | version = "1.3.0" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 445 | 446 | [[package]] 447 | name = "futures" 448 | version = "0.3.31" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 451 | dependencies = [ 452 | "futures-channel", 453 | "futures-core", 454 | "futures-executor", 455 | "futures-io", 456 | "futures-sink", 457 | "futures-task", 458 | "futures-util", 459 | ] 460 | 461 | [[package]] 462 | name = "futures-channel" 463 | version = "0.3.31" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 466 | dependencies = [ 467 | "futures-core", 468 | "futures-sink", 469 | ] 470 | 471 | [[package]] 472 | name = "futures-core" 473 | version = "0.3.31" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 476 | 477 | [[package]] 478 | name = "futures-executor" 479 | version = "0.3.31" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 482 | dependencies = [ 483 | "futures-core", 484 | "futures-task", 485 | "futures-util", 486 | ] 487 | 488 | [[package]] 489 | name = "futures-io" 490 | version = "0.3.31" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 493 | 494 | [[package]] 495 | name = "futures-macro" 496 | version = "0.3.31" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 499 | dependencies = [ 500 | "proc-macro2", 501 | "quote", 502 | "syn", 503 | ] 504 | 505 | [[package]] 506 | name = "futures-sink" 507 | version = "0.3.31" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 510 | 511 | [[package]] 512 | name = "futures-task" 513 | version = "0.3.31" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 516 | 517 | [[package]] 518 | name = "futures-util" 519 | version = "0.3.31" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 522 | dependencies = [ 523 | "futures-channel", 524 | "futures-core", 525 | "futures-io", 526 | "futures-macro", 527 | "futures-sink", 528 | "futures-task", 529 | "memchr", 530 | "pin-project-lite", 531 | "pin-utils", 532 | "slab", 533 | ] 534 | 535 | [[package]] 536 | name = "getrandom" 537 | version = "0.2.15" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 540 | dependencies = [ 541 | "cfg-if", 542 | "js-sys", 543 | "libc", 544 | "wasi", 545 | "wasm-bindgen", 546 | ] 547 | 548 | [[package]] 549 | name = "gimli" 550 | version = "0.31.1" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 553 | 554 | [[package]] 555 | name = "gio-sys" 556 | version = "0.21.0" 557 | source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=main#89254b3e5a594867cc917e83987df43c5191a51f" 558 | dependencies = [ 559 | "glib-sys", 560 | "gobject-sys", 561 | "libc", 562 | "system-deps", 563 | "windows-sys 0.59.0", 564 | ] 565 | 566 | [[package]] 567 | name = "glib" 568 | version = "0.21.0" 569 | source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=main#89254b3e5a594867cc917e83987df43c5191a51f" 570 | dependencies = [ 571 | "bitflags", 572 | "futures-channel", 573 | "futures-core", 574 | "futures-executor", 575 | "futures-task", 576 | "futures-util", 577 | "gio-sys", 578 | "glib-macros", 579 | "glib-sys", 580 | "gobject-sys", 581 | "libc", 582 | "memchr", 583 | "smallvec", 584 | ] 585 | 586 | [[package]] 587 | name = "glib-macros" 588 | version = "0.21.0" 589 | source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=main#89254b3e5a594867cc917e83987df43c5191a51f" 590 | dependencies = [ 591 | "heck", 592 | "proc-macro-crate", 593 | "proc-macro2", 594 | "quote", 595 | "syn", 596 | ] 597 | 598 | [[package]] 599 | name = "glib-sys" 600 | version = "0.21.0" 601 | source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=main#89254b3e5a594867cc917e83987df43c5191a51f" 602 | dependencies = [ 603 | "libc", 604 | "system-deps", 605 | ] 606 | 607 | [[package]] 608 | name = "glob" 609 | version = "0.3.2" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 612 | 613 | [[package]] 614 | name = "gobject-sys" 615 | version = "0.21.0" 616 | source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=main#89254b3e5a594867cc917e83987df43c5191a51f" 617 | dependencies = [ 618 | "glib-sys", 619 | "libc", 620 | "system-deps", 621 | ] 622 | 623 | [[package]] 624 | name = "gst-moq" 625 | version = "0.1.0" 626 | dependencies = [ 627 | "anyhow", 628 | "bytes", 629 | "clap", 630 | "ctrlc", 631 | "env_logger", 632 | "futures", 633 | "gst-plugin-version-helper", 634 | "gstreamer", 635 | "gstreamer-app", 636 | "gstreamer-audio", 637 | "gstreamer-base", 638 | "gstreamer-pbutils", 639 | "gstreamer-video", 640 | "http", 641 | "moq-catalog", 642 | "moq-native-ietf", 643 | "moq-transport", 644 | "once_cell", 645 | "quinn", 646 | "quinn-proto", 647 | "rustls", 648 | "rustls-pemfile", 649 | "rustls-pki-types", 650 | "serde_json", 651 | "thiserror 2.0.9", 652 | "tokio", 653 | "url", 654 | "web-transport-quinn", 655 | ] 656 | 657 | [[package]] 658 | name = "gst-plugin-version-helper" 659 | version = "0.8.2" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "4e5e874f1660252fd2ec81c602066df3633b3a6fcbe2b196f7f93c27cf069b2a" 662 | dependencies = [ 663 | "chrono", 664 | "toml_edit", 665 | ] 666 | 667 | [[package]] 668 | name = "gstreamer" 669 | version = "0.24.0" 670 | source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#c365b87781005eb5f9aa69a02f720c91db6db3ac" 671 | dependencies = [ 672 | "cfg-if", 673 | "futures-channel", 674 | "futures-core", 675 | "futures-util", 676 | "glib", 677 | "gstreamer-sys", 678 | "itertools 0.14.0", 679 | "kstring", 680 | "libc", 681 | "muldiv", 682 | "num-integer", 683 | "num-rational", 684 | "option-operations", 685 | "paste", 686 | "pin-project-lite", 687 | "smallvec", 688 | "thiserror 2.0.9", 689 | ] 690 | 691 | [[package]] 692 | name = "gstreamer-app" 693 | version = "0.24.0" 694 | source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#c365b87781005eb5f9aa69a02f720c91db6db3ac" 695 | dependencies = [ 696 | "futures-core", 697 | "futures-sink", 698 | "glib", 699 | "gstreamer", 700 | "gstreamer-app-sys", 701 | "gstreamer-base", 702 | "libc", 703 | ] 704 | 705 | [[package]] 706 | name = "gstreamer-app-sys" 707 | version = "0.24.0" 708 | source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#c365b87781005eb5f9aa69a02f720c91db6db3ac" 709 | dependencies = [ 710 | "glib-sys", 711 | "gstreamer-base-sys", 712 | "gstreamer-sys", 713 | "libc", 714 | "system-deps", 715 | ] 716 | 717 | [[package]] 718 | name = "gstreamer-audio" 719 | version = "0.24.0" 720 | source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#c365b87781005eb5f9aa69a02f720c91db6db3ac" 721 | dependencies = [ 722 | "cfg-if", 723 | "glib", 724 | "gstreamer", 725 | "gstreamer-audio-sys", 726 | "gstreamer-base", 727 | "libc", 728 | "smallvec", 729 | ] 730 | 731 | [[package]] 732 | name = "gstreamer-audio-sys" 733 | version = "0.24.0" 734 | source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#c365b87781005eb5f9aa69a02f720c91db6db3ac" 735 | dependencies = [ 736 | "glib-sys", 737 | "gobject-sys", 738 | "gstreamer-base-sys", 739 | "gstreamer-sys", 740 | "libc", 741 | "system-deps", 742 | ] 743 | 744 | [[package]] 745 | name = "gstreamer-base" 746 | version = "0.24.0" 747 | source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#c365b87781005eb5f9aa69a02f720c91db6db3ac" 748 | dependencies = [ 749 | "atomic_refcell", 750 | "cfg-if", 751 | "glib", 752 | "gstreamer", 753 | "gstreamer-base-sys", 754 | "libc", 755 | ] 756 | 757 | [[package]] 758 | name = "gstreamer-base-sys" 759 | version = "0.24.0" 760 | source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#c365b87781005eb5f9aa69a02f720c91db6db3ac" 761 | dependencies = [ 762 | "glib-sys", 763 | "gobject-sys", 764 | "gstreamer-sys", 765 | "libc", 766 | "system-deps", 767 | ] 768 | 769 | [[package]] 770 | name = "gstreamer-pbutils" 771 | version = "0.24.0" 772 | source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#c365b87781005eb5f9aa69a02f720c91db6db3ac" 773 | dependencies = [ 774 | "glib", 775 | "gstreamer", 776 | "gstreamer-audio", 777 | "gstreamer-pbutils-sys", 778 | "gstreamer-video", 779 | "libc", 780 | "thiserror 2.0.9", 781 | ] 782 | 783 | [[package]] 784 | name = "gstreamer-pbutils-sys" 785 | version = "0.24.0" 786 | source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#c365b87781005eb5f9aa69a02f720c91db6db3ac" 787 | dependencies = [ 788 | "glib-sys", 789 | "gobject-sys", 790 | "gstreamer-audio-sys", 791 | "gstreamer-sys", 792 | "gstreamer-video-sys", 793 | "libc", 794 | "system-deps", 795 | ] 796 | 797 | [[package]] 798 | name = "gstreamer-sys" 799 | version = "0.24.0" 800 | source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#c365b87781005eb5f9aa69a02f720c91db6db3ac" 801 | dependencies = [ 802 | "cfg-if", 803 | "glib-sys", 804 | "gobject-sys", 805 | "libc", 806 | "system-deps", 807 | ] 808 | 809 | [[package]] 810 | name = "gstreamer-video" 811 | version = "0.24.0" 812 | source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#c365b87781005eb5f9aa69a02f720c91db6db3ac" 813 | dependencies = [ 814 | "cfg-if", 815 | "futures-channel", 816 | "glib", 817 | "gstreamer", 818 | "gstreamer-base", 819 | "gstreamer-video-sys", 820 | "libc", 821 | "thiserror 2.0.9", 822 | ] 823 | 824 | [[package]] 825 | name = "gstreamer-video-sys" 826 | version = "0.24.0" 827 | source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#c365b87781005eb5f9aa69a02f720c91db6db3ac" 828 | dependencies = [ 829 | "glib-sys", 830 | "gobject-sys", 831 | "gstreamer-base-sys", 832 | "gstreamer-sys", 833 | "libc", 834 | "system-deps", 835 | ] 836 | 837 | [[package]] 838 | name = "hashbrown" 839 | version = "0.15.2" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 842 | 843 | [[package]] 844 | name = "heck" 845 | version = "0.5.0" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 848 | 849 | [[package]] 850 | name = "hex" 851 | version = "0.4.3" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 854 | 855 | [[package]] 856 | name = "home" 857 | version = "0.5.11" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 860 | dependencies = [ 861 | "windows-sys 0.59.0", 862 | ] 863 | 864 | [[package]] 865 | name = "http" 866 | version = "1.2.0" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" 869 | dependencies = [ 870 | "bytes", 871 | "fnv", 872 | "itoa", 873 | ] 874 | 875 | [[package]] 876 | name = "humantime" 877 | version = "2.1.0" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 880 | 881 | [[package]] 882 | name = "iana-time-zone" 883 | version = "0.1.61" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 886 | dependencies = [ 887 | "android_system_properties", 888 | "core-foundation-sys", 889 | "iana-time-zone-haiku", 890 | "js-sys", 891 | "wasm-bindgen", 892 | "windows-core", 893 | ] 894 | 895 | [[package]] 896 | name = "iana-time-zone-haiku" 897 | version = "0.1.2" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 900 | dependencies = [ 901 | "cc", 902 | ] 903 | 904 | [[package]] 905 | name = "icu_collections" 906 | version = "1.5.0" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 909 | dependencies = [ 910 | "displaydoc", 911 | "yoke", 912 | "zerofrom", 913 | "zerovec", 914 | ] 915 | 916 | [[package]] 917 | name = "icu_locid" 918 | version = "1.5.0" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 921 | dependencies = [ 922 | "displaydoc", 923 | "litemap", 924 | "tinystr", 925 | "writeable", 926 | "zerovec", 927 | ] 928 | 929 | [[package]] 930 | name = "icu_locid_transform" 931 | version = "1.5.0" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 934 | dependencies = [ 935 | "displaydoc", 936 | "icu_locid", 937 | "icu_locid_transform_data", 938 | "icu_provider", 939 | "tinystr", 940 | "zerovec", 941 | ] 942 | 943 | [[package]] 944 | name = "icu_locid_transform_data" 945 | version = "1.5.0" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 948 | 949 | [[package]] 950 | name = "icu_normalizer" 951 | version = "1.5.0" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 954 | dependencies = [ 955 | "displaydoc", 956 | "icu_collections", 957 | "icu_normalizer_data", 958 | "icu_properties", 959 | "icu_provider", 960 | "smallvec", 961 | "utf16_iter", 962 | "utf8_iter", 963 | "write16", 964 | "zerovec", 965 | ] 966 | 967 | [[package]] 968 | name = "icu_normalizer_data" 969 | version = "1.5.0" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 972 | 973 | [[package]] 974 | name = "icu_properties" 975 | version = "1.5.1" 976 | source = "registry+https://github.com/rust-lang/crates.io-index" 977 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 978 | dependencies = [ 979 | "displaydoc", 980 | "icu_collections", 981 | "icu_locid_transform", 982 | "icu_properties_data", 983 | "icu_provider", 984 | "tinystr", 985 | "zerovec", 986 | ] 987 | 988 | [[package]] 989 | name = "icu_properties_data" 990 | version = "1.5.0" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 993 | 994 | [[package]] 995 | name = "icu_provider" 996 | version = "1.5.0" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 999 | dependencies = [ 1000 | "displaydoc", 1001 | "icu_locid", 1002 | "icu_provider_macros", 1003 | "stable_deref_trait", 1004 | "tinystr", 1005 | "writeable", 1006 | "yoke", 1007 | "zerofrom", 1008 | "zerovec", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "icu_provider_macros" 1013 | version = "1.5.0" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 1016 | dependencies = [ 1017 | "proc-macro2", 1018 | "quote", 1019 | "syn", 1020 | ] 1021 | 1022 | [[package]] 1023 | name = "idna" 1024 | version = "1.0.3" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 1027 | dependencies = [ 1028 | "idna_adapter", 1029 | "smallvec", 1030 | "utf8_iter", 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "idna_adapter" 1035 | version = "1.2.0" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 1038 | dependencies = [ 1039 | "icu_normalizer", 1040 | "icu_properties", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "indexmap" 1045 | version = "2.7.0" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" 1048 | dependencies = [ 1049 | "equivalent", 1050 | "hashbrown", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "is_terminal_polyfill" 1055 | version = "1.70.1" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 1058 | 1059 | [[package]] 1060 | name = "itertools" 1061 | version = "0.12.1" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 1064 | dependencies = [ 1065 | "either", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "itertools" 1070 | version = "0.14.0" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 1073 | dependencies = [ 1074 | "either", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "itoa" 1079 | version = "1.0.14" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 1082 | 1083 | [[package]] 1084 | name = "jni" 1085 | version = "0.19.0" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" 1088 | dependencies = [ 1089 | "cesu8", 1090 | "combine", 1091 | "jni-sys", 1092 | "log", 1093 | "thiserror 1.0.69", 1094 | "walkdir", 1095 | ] 1096 | 1097 | [[package]] 1098 | name = "jni-sys" 1099 | version = "0.3.0" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 1102 | 1103 | [[package]] 1104 | name = "jobserver" 1105 | version = "0.1.32" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 1108 | dependencies = [ 1109 | "libc", 1110 | ] 1111 | 1112 | [[package]] 1113 | name = "js-sys" 1114 | version = "0.3.76" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" 1117 | dependencies = [ 1118 | "once_cell", 1119 | "wasm-bindgen", 1120 | ] 1121 | 1122 | [[package]] 1123 | name = "kstring" 1124 | version = "2.0.2" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" 1127 | dependencies = [ 1128 | "static_assertions", 1129 | ] 1130 | 1131 | [[package]] 1132 | name = "lazy_static" 1133 | version = "1.5.0" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1136 | 1137 | [[package]] 1138 | name = "lazycell" 1139 | version = "1.3.0" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 1142 | 1143 | [[package]] 1144 | name = "libc" 1145 | version = "0.2.169" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 1148 | 1149 | [[package]] 1150 | name = "libloading" 1151 | version = "0.8.6" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" 1154 | dependencies = [ 1155 | "cfg-if", 1156 | "windows-targets", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "linux-raw-sys" 1161 | version = "0.4.14" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 1164 | 1165 | [[package]] 1166 | name = "litemap" 1167 | version = "0.7.4" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" 1170 | 1171 | [[package]] 1172 | name = "lock_api" 1173 | version = "0.4.12" 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" 1175 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 1176 | dependencies = [ 1177 | "autocfg", 1178 | "scopeguard", 1179 | ] 1180 | 1181 | [[package]] 1182 | name = "log" 1183 | version = "0.4.22" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 1186 | 1187 | [[package]] 1188 | name = "memchr" 1189 | version = "2.7.4" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1192 | 1193 | [[package]] 1194 | name = "minimal-lexical" 1195 | version = "0.2.1" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1198 | 1199 | [[package]] 1200 | name = "miniz_oxide" 1201 | version = "0.8.2" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" 1204 | dependencies = [ 1205 | "adler2", 1206 | ] 1207 | 1208 | [[package]] 1209 | name = "mio" 1210 | version = "1.0.3" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 1213 | dependencies = [ 1214 | "libc", 1215 | "wasi", 1216 | "windows-sys 0.52.0", 1217 | ] 1218 | 1219 | [[package]] 1220 | name = "moq-catalog" 1221 | version = "0.2.2" 1222 | source = "git+https://github.com/englishm/moq-rs.git?rev=ebc843de8504e37d36c3134a1181513ebdf7a34a#ebc843de8504e37d36c3134a1181513ebdf7a34a" 1223 | dependencies = [ 1224 | "serde", 1225 | ] 1226 | 1227 | [[package]] 1228 | name = "moq-native-ietf" 1229 | version = "0.5.4" 1230 | source = "git+https://github.com/englishm/moq-rs.git?rev=ebc843de8504e37d36c3134a1181513ebdf7a34a#ebc843de8504e37d36c3134a1181513ebdf7a34a" 1231 | dependencies = [ 1232 | "anyhow", 1233 | "clap", 1234 | "futures", 1235 | "hex", 1236 | "log", 1237 | "moq-transport", 1238 | "quinn", 1239 | "ring", 1240 | "rustls", 1241 | "rustls-native-certs", 1242 | "rustls-pemfile", 1243 | "tokio", 1244 | "url", 1245 | "web-transport", 1246 | "web-transport-quinn", 1247 | "webpki", 1248 | ] 1249 | 1250 | [[package]] 1251 | name = "moq-transport" 1252 | version = "0.10.0" 1253 | source = "git+https://github.com/englishm/moq-rs.git?rev=ebc843de8504e37d36c3134a1181513ebdf7a34a#ebc843de8504e37d36c3134a1181513ebdf7a34a" 1254 | dependencies = [ 1255 | "bytes", 1256 | "futures", 1257 | "log", 1258 | "paste", 1259 | "thiserror 1.0.69", 1260 | "tokio", 1261 | "web-transport", 1262 | ] 1263 | 1264 | [[package]] 1265 | name = "muldiv" 1266 | version = "1.0.1" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" 1269 | 1270 | [[package]] 1271 | name = "nix" 1272 | version = "0.29.0" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 1275 | dependencies = [ 1276 | "bitflags", 1277 | "cfg-if", 1278 | "cfg_aliases", 1279 | "libc", 1280 | ] 1281 | 1282 | [[package]] 1283 | name = "nom" 1284 | version = "7.1.3" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1287 | dependencies = [ 1288 | "memchr", 1289 | "minimal-lexical", 1290 | ] 1291 | 1292 | [[package]] 1293 | name = "num-bigint" 1294 | version = "0.4.6" 1295 | source = "registry+https://github.com/rust-lang/crates.io-index" 1296 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 1297 | dependencies = [ 1298 | "num-integer", 1299 | "num-traits", 1300 | ] 1301 | 1302 | [[package]] 1303 | name = "num-integer" 1304 | version = "0.1.46" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1307 | dependencies = [ 1308 | "num-traits", 1309 | ] 1310 | 1311 | [[package]] 1312 | name = "num-rational" 1313 | version = "0.4.2" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" 1316 | dependencies = [ 1317 | "num-integer", 1318 | "num-traits", 1319 | ] 1320 | 1321 | [[package]] 1322 | name = "num-traits" 1323 | version = "0.2.19" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1326 | dependencies = [ 1327 | "autocfg", 1328 | ] 1329 | 1330 | [[package]] 1331 | name = "object" 1332 | version = "0.36.7" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1335 | dependencies = [ 1336 | "memchr", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "once_cell" 1341 | version = "1.20.2" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 1344 | 1345 | [[package]] 1346 | name = "openssl-probe" 1347 | version = "0.1.5" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1350 | 1351 | [[package]] 1352 | name = "option-operations" 1353 | version = "0.5.0" 1354 | source = "registry+https://github.com/rust-lang/crates.io-index" 1355 | checksum = "7c26d27bb1aeab65138e4bf7666045169d1717febcc9ff870166be8348b223d0" 1356 | dependencies = [ 1357 | "paste", 1358 | ] 1359 | 1360 | [[package]] 1361 | name = "parking_lot" 1362 | version = "0.12.3" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1365 | dependencies = [ 1366 | "lock_api", 1367 | "parking_lot_core", 1368 | ] 1369 | 1370 | [[package]] 1371 | name = "parking_lot_core" 1372 | version = "0.9.10" 1373 | source = "registry+https://github.com/rust-lang/crates.io-index" 1374 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1375 | dependencies = [ 1376 | "cfg-if", 1377 | "libc", 1378 | "redox_syscall", 1379 | "smallvec", 1380 | "windows-targets", 1381 | ] 1382 | 1383 | [[package]] 1384 | name = "paste" 1385 | version = "1.0.15" 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" 1387 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1388 | 1389 | [[package]] 1390 | name = "percent-encoding" 1391 | version = "2.3.1" 1392 | source = "registry+https://github.com/rust-lang/crates.io-index" 1393 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1394 | 1395 | [[package]] 1396 | name = "pin-project-lite" 1397 | version = "0.2.15" 1398 | source = "registry+https://github.com/rust-lang/crates.io-index" 1399 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 1400 | 1401 | [[package]] 1402 | name = "pin-utils" 1403 | version = "0.1.0" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1406 | 1407 | [[package]] 1408 | name = "pkg-config" 1409 | version = "0.3.31" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 1412 | 1413 | [[package]] 1414 | name = "ppv-lite86" 1415 | version = "0.2.20" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 1418 | dependencies = [ 1419 | "zerocopy", 1420 | ] 1421 | 1422 | [[package]] 1423 | name = "prettyplease" 1424 | version = "0.2.25" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" 1427 | dependencies = [ 1428 | "proc-macro2", 1429 | "syn", 1430 | ] 1431 | 1432 | [[package]] 1433 | name = "proc-macro-crate" 1434 | version = "3.2.0" 1435 | source = "registry+https://github.com/rust-lang/crates.io-index" 1436 | checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" 1437 | dependencies = [ 1438 | "toml_edit", 1439 | ] 1440 | 1441 | [[package]] 1442 | name = "proc-macro2" 1443 | version = "1.0.92" 1444 | source = "registry+https://github.com/rust-lang/crates.io-index" 1445 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 1446 | dependencies = [ 1447 | "unicode-ident", 1448 | ] 1449 | 1450 | [[package]] 1451 | name = "quinn" 1452 | version = "0.11.6" 1453 | source = "registry+https://github.com/rust-lang/crates.io-index" 1454 | checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" 1455 | dependencies = [ 1456 | "bytes", 1457 | "pin-project-lite", 1458 | "quinn-proto", 1459 | "quinn-udp", 1460 | "rustc-hash 2.1.0", 1461 | "rustls", 1462 | "socket2", 1463 | "thiserror 2.0.9", 1464 | "tokio", 1465 | "tracing", 1466 | ] 1467 | 1468 | [[package]] 1469 | name = "quinn-proto" 1470 | version = "0.11.9" 1471 | source = "registry+https://github.com/rust-lang/crates.io-index" 1472 | checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" 1473 | dependencies = [ 1474 | "bytes", 1475 | "getrandom", 1476 | "rand", 1477 | "ring", 1478 | "rustc-hash 2.1.0", 1479 | "rustls", 1480 | "rustls-pki-types", 1481 | "rustls-platform-verifier", 1482 | "slab", 1483 | "thiserror 2.0.9", 1484 | "tinyvec", 1485 | "tracing", 1486 | "web-time", 1487 | ] 1488 | 1489 | [[package]] 1490 | name = "quinn-udp" 1491 | version = "0.5.9" 1492 | source = "registry+https://github.com/rust-lang/crates.io-index" 1493 | checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" 1494 | dependencies = [ 1495 | "cfg_aliases", 1496 | "libc", 1497 | "once_cell", 1498 | "socket2", 1499 | "tracing", 1500 | "windows-sys 0.59.0", 1501 | ] 1502 | 1503 | [[package]] 1504 | name = "quote" 1505 | version = "1.0.38" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 1508 | dependencies = [ 1509 | "proc-macro2", 1510 | ] 1511 | 1512 | [[package]] 1513 | name = "rand" 1514 | version = "0.8.5" 1515 | source = "registry+https://github.com/rust-lang/crates.io-index" 1516 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1517 | dependencies = [ 1518 | "libc", 1519 | "rand_chacha", 1520 | "rand_core", 1521 | ] 1522 | 1523 | [[package]] 1524 | name = "rand_chacha" 1525 | version = "0.3.1" 1526 | source = "registry+https://github.com/rust-lang/crates.io-index" 1527 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1528 | dependencies = [ 1529 | "ppv-lite86", 1530 | "rand_core", 1531 | ] 1532 | 1533 | [[package]] 1534 | name = "rand_core" 1535 | version = "0.6.4" 1536 | source = "registry+https://github.com/rust-lang/crates.io-index" 1537 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1538 | dependencies = [ 1539 | "getrandom", 1540 | ] 1541 | 1542 | [[package]] 1543 | name = "redox_syscall" 1544 | version = "0.5.8" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" 1547 | dependencies = [ 1548 | "bitflags", 1549 | ] 1550 | 1551 | [[package]] 1552 | name = "regex" 1553 | version = "1.11.1" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1556 | dependencies = [ 1557 | "aho-corasick", 1558 | "memchr", 1559 | "regex-automata", 1560 | "regex-syntax", 1561 | ] 1562 | 1563 | [[package]] 1564 | name = "regex-automata" 1565 | version = "0.4.9" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1568 | dependencies = [ 1569 | "aho-corasick", 1570 | "memchr", 1571 | "regex-syntax", 1572 | ] 1573 | 1574 | [[package]] 1575 | name = "regex-syntax" 1576 | version = "0.8.5" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1579 | 1580 | [[package]] 1581 | name = "ring" 1582 | version = "0.17.8" 1583 | source = "registry+https://github.com/rust-lang/crates.io-index" 1584 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 1585 | dependencies = [ 1586 | "cc", 1587 | "cfg-if", 1588 | "getrandom", 1589 | "libc", 1590 | "spin", 1591 | "untrusted", 1592 | "windows-sys 0.52.0", 1593 | ] 1594 | 1595 | [[package]] 1596 | name = "rustc-demangle" 1597 | version = "0.1.24" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1600 | 1601 | [[package]] 1602 | name = "rustc-hash" 1603 | version = "1.1.0" 1604 | source = "registry+https://github.com/rust-lang/crates.io-index" 1605 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1606 | 1607 | [[package]] 1608 | name = "rustc-hash" 1609 | version = "2.1.0" 1610 | source = "registry+https://github.com/rust-lang/crates.io-index" 1611 | checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" 1612 | 1613 | [[package]] 1614 | name = "rustix" 1615 | version = "0.38.42" 1616 | source = "registry+https://github.com/rust-lang/crates.io-index" 1617 | checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" 1618 | dependencies = [ 1619 | "bitflags", 1620 | "errno", 1621 | "libc", 1622 | "linux-raw-sys", 1623 | "windows-sys 0.59.0", 1624 | ] 1625 | 1626 | [[package]] 1627 | name = "rustls" 1628 | version = "0.23.20" 1629 | source = "registry+https://github.com/rust-lang/crates.io-index" 1630 | checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" 1631 | dependencies = [ 1632 | "aws-lc-rs", 1633 | "log", 1634 | "once_cell", 1635 | "ring", 1636 | "rustls-pki-types", 1637 | "rustls-webpki", 1638 | "subtle", 1639 | "zeroize", 1640 | ] 1641 | 1642 | [[package]] 1643 | name = "rustls-native-certs" 1644 | version = "0.7.3" 1645 | source = "registry+https://github.com/rust-lang/crates.io-index" 1646 | checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" 1647 | dependencies = [ 1648 | "openssl-probe", 1649 | "rustls-pemfile", 1650 | "rustls-pki-types", 1651 | "schannel", 1652 | "security-framework", 1653 | ] 1654 | 1655 | [[package]] 1656 | name = "rustls-pemfile" 1657 | version = "2.2.0" 1658 | source = "registry+https://github.com/rust-lang/crates.io-index" 1659 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 1660 | dependencies = [ 1661 | "rustls-pki-types", 1662 | ] 1663 | 1664 | [[package]] 1665 | name = "rustls-pki-types" 1666 | version = "1.10.1" 1667 | source = "registry+https://github.com/rust-lang/crates.io-index" 1668 | checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" 1669 | dependencies = [ 1670 | "web-time", 1671 | ] 1672 | 1673 | [[package]] 1674 | name = "rustls-platform-verifier" 1675 | version = "0.4.0" 1676 | source = "registry+https://github.com/rust-lang/crates.io-index" 1677 | checksum = "a4c7dc240fec5517e6c4eab3310438636cfe6391dfc345ba013109909a90d136" 1678 | dependencies = [ 1679 | "core-foundation", 1680 | "core-foundation-sys", 1681 | "jni", 1682 | "log", 1683 | "once_cell", 1684 | "rustls", 1685 | "rustls-native-certs", 1686 | "rustls-platform-verifier-android", 1687 | "rustls-webpki", 1688 | "security-framework", 1689 | "security-framework-sys", 1690 | "webpki-root-certs", 1691 | "windows-sys 0.52.0", 1692 | ] 1693 | 1694 | [[package]] 1695 | name = "rustls-platform-verifier-android" 1696 | version = "0.1.1" 1697 | source = "registry+https://github.com/rust-lang/crates.io-index" 1698 | checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" 1699 | 1700 | [[package]] 1701 | name = "rustls-webpki" 1702 | version = "0.102.8" 1703 | source = "registry+https://github.com/rust-lang/crates.io-index" 1704 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 1705 | dependencies = [ 1706 | "aws-lc-rs", 1707 | "ring", 1708 | "rustls-pki-types", 1709 | "untrusted", 1710 | ] 1711 | 1712 | [[package]] 1713 | name = "ryu" 1714 | version = "1.0.18" 1715 | source = "registry+https://github.com/rust-lang/crates.io-index" 1716 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1717 | 1718 | [[package]] 1719 | name = "same-file" 1720 | version = "1.0.6" 1721 | source = "registry+https://github.com/rust-lang/crates.io-index" 1722 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1723 | dependencies = [ 1724 | "winapi-util", 1725 | ] 1726 | 1727 | [[package]] 1728 | name = "schannel" 1729 | version = "0.1.27" 1730 | source = "registry+https://github.com/rust-lang/crates.io-index" 1731 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 1732 | dependencies = [ 1733 | "windows-sys 0.59.0", 1734 | ] 1735 | 1736 | [[package]] 1737 | name = "scopeguard" 1738 | version = "1.2.0" 1739 | source = "registry+https://github.com/rust-lang/crates.io-index" 1740 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1741 | 1742 | [[package]] 1743 | name = "security-framework" 1744 | version = "2.11.1" 1745 | source = "registry+https://github.com/rust-lang/crates.io-index" 1746 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1747 | dependencies = [ 1748 | "bitflags", 1749 | "core-foundation", 1750 | "core-foundation-sys", 1751 | "libc", 1752 | "num-bigint", 1753 | "security-framework-sys", 1754 | ] 1755 | 1756 | [[package]] 1757 | name = "security-framework-sys" 1758 | version = "2.13.0" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" 1761 | dependencies = [ 1762 | "core-foundation-sys", 1763 | "libc", 1764 | ] 1765 | 1766 | [[package]] 1767 | name = "serde" 1768 | version = "1.0.217" 1769 | source = "registry+https://github.com/rust-lang/crates.io-index" 1770 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 1771 | dependencies = [ 1772 | "serde_derive", 1773 | ] 1774 | 1775 | [[package]] 1776 | name = "serde_derive" 1777 | version = "1.0.217" 1778 | source = "registry+https://github.com/rust-lang/crates.io-index" 1779 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 1780 | dependencies = [ 1781 | "proc-macro2", 1782 | "quote", 1783 | "syn", 1784 | ] 1785 | 1786 | [[package]] 1787 | name = "serde_json" 1788 | version = "1.0.134" 1789 | source = "registry+https://github.com/rust-lang/crates.io-index" 1790 | checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" 1791 | dependencies = [ 1792 | "itoa", 1793 | "memchr", 1794 | "ryu", 1795 | "serde", 1796 | ] 1797 | 1798 | [[package]] 1799 | name = "serde_spanned" 1800 | version = "0.6.8" 1801 | source = "registry+https://github.com/rust-lang/crates.io-index" 1802 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 1803 | dependencies = [ 1804 | "serde", 1805 | ] 1806 | 1807 | [[package]] 1808 | name = "shlex" 1809 | version = "1.3.0" 1810 | source = "registry+https://github.com/rust-lang/crates.io-index" 1811 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1812 | 1813 | [[package]] 1814 | name = "signal-hook-registry" 1815 | version = "1.4.2" 1816 | source = "registry+https://github.com/rust-lang/crates.io-index" 1817 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1818 | dependencies = [ 1819 | "libc", 1820 | ] 1821 | 1822 | [[package]] 1823 | name = "slab" 1824 | version = "0.4.9" 1825 | source = "registry+https://github.com/rust-lang/crates.io-index" 1826 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1827 | dependencies = [ 1828 | "autocfg", 1829 | ] 1830 | 1831 | [[package]] 1832 | name = "smallvec" 1833 | version = "1.13.2" 1834 | source = "registry+https://github.com/rust-lang/crates.io-index" 1835 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1836 | 1837 | [[package]] 1838 | name = "socket2" 1839 | version = "0.5.8" 1840 | source = "registry+https://github.com/rust-lang/crates.io-index" 1841 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" 1842 | dependencies = [ 1843 | "libc", 1844 | "windows-sys 0.52.0", 1845 | ] 1846 | 1847 | [[package]] 1848 | name = "spin" 1849 | version = "0.9.8" 1850 | source = "registry+https://github.com/rust-lang/crates.io-index" 1851 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1852 | 1853 | [[package]] 1854 | name = "stable_deref_trait" 1855 | version = "1.2.0" 1856 | source = "registry+https://github.com/rust-lang/crates.io-index" 1857 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1858 | 1859 | [[package]] 1860 | name = "static_assertions" 1861 | version = "1.1.0" 1862 | source = "registry+https://github.com/rust-lang/crates.io-index" 1863 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1864 | 1865 | [[package]] 1866 | name = "strsim" 1867 | version = "0.11.1" 1868 | source = "registry+https://github.com/rust-lang/crates.io-index" 1869 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1870 | 1871 | [[package]] 1872 | name = "subtle" 1873 | version = "2.6.1" 1874 | source = "registry+https://github.com/rust-lang/crates.io-index" 1875 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1876 | 1877 | [[package]] 1878 | name = "syn" 1879 | version = "2.0.94" 1880 | source = "registry+https://github.com/rust-lang/crates.io-index" 1881 | checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" 1882 | dependencies = [ 1883 | "proc-macro2", 1884 | "quote", 1885 | "unicode-ident", 1886 | ] 1887 | 1888 | [[package]] 1889 | name = "synstructure" 1890 | version = "0.13.1" 1891 | source = "registry+https://github.com/rust-lang/crates.io-index" 1892 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1893 | dependencies = [ 1894 | "proc-macro2", 1895 | "quote", 1896 | "syn", 1897 | ] 1898 | 1899 | [[package]] 1900 | name = "system-deps" 1901 | version = "7.0.3" 1902 | source = "registry+https://github.com/rust-lang/crates.io-index" 1903 | checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" 1904 | dependencies = [ 1905 | "cfg-expr", 1906 | "heck", 1907 | "pkg-config", 1908 | "toml", 1909 | "version-compare", 1910 | ] 1911 | 1912 | [[package]] 1913 | name = "target-lexicon" 1914 | version = "0.12.16" 1915 | source = "registry+https://github.com/rust-lang/crates.io-index" 1916 | checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" 1917 | 1918 | [[package]] 1919 | name = "thiserror" 1920 | version = "1.0.69" 1921 | source = "registry+https://github.com/rust-lang/crates.io-index" 1922 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1923 | dependencies = [ 1924 | "thiserror-impl 1.0.69", 1925 | ] 1926 | 1927 | [[package]] 1928 | name = "thiserror" 1929 | version = "2.0.9" 1930 | source = "registry+https://github.com/rust-lang/crates.io-index" 1931 | checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" 1932 | dependencies = [ 1933 | "thiserror-impl 2.0.9", 1934 | ] 1935 | 1936 | [[package]] 1937 | name = "thiserror-impl" 1938 | version = "1.0.69" 1939 | source = "registry+https://github.com/rust-lang/crates.io-index" 1940 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1941 | dependencies = [ 1942 | "proc-macro2", 1943 | "quote", 1944 | "syn", 1945 | ] 1946 | 1947 | [[package]] 1948 | name = "thiserror-impl" 1949 | version = "2.0.9" 1950 | source = "registry+https://github.com/rust-lang/crates.io-index" 1951 | checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" 1952 | dependencies = [ 1953 | "proc-macro2", 1954 | "quote", 1955 | "syn", 1956 | ] 1957 | 1958 | [[package]] 1959 | name = "tinystr" 1960 | version = "0.7.6" 1961 | source = "registry+https://github.com/rust-lang/crates.io-index" 1962 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1963 | dependencies = [ 1964 | "displaydoc", 1965 | "zerovec", 1966 | ] 1967 | 1968 | [[package]] 1969 | name = "tinyvec" 1970 | version = "1.8.1" 1971 | source = "registry+https://github.com/rust-lang/crates.io-index" 1972 | checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" 1973 | dependencies = [ 1974 | "tinyvec_macros", 1975 | ] 1976 | 1977 | [[package]] 1978 | name = "tinyvec_macros" 1979 | version = "0.1.1" 1980 | source = "registry+https://github.com/rust-lang/crates.io-index" 1981 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1982 | 1983 | [[package]] 1984 | name = "tokio" 1985 | version = "1.42.0" 1986 | source = "registry+https://github.com/rust-lang/crates.io-index" 1987 | checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" 1988 | dependencies = [ 1989 | "backtrace", 1990 | "bytes", 1991 | "libc", 1992 | "mio", 1993 | "parking_lot", 1994 | "pin-project-lite", 1995 | "signal-hook-registry", 1996 | "socket2", 1997 | "tokio-macros", 1998 | "windows-sys 0.52.0", 1999 | ] 2000 | 2001 | [[package]] 2002 | name = "tokio-macros" 2003 | version = "2.4.0" 2004 | source = "registry+https://github.com/rust-lang/crates.io-index" 2005 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 2006 | dependencies = [ 2007 | "proc-macro2", 2008 | "quote", 2009 | "syn", 2010 | ] 2011 | 2012 | [[package]] 2013 | name = "toml" 2014 | version = "0.8.19" 2015 | source = "registry+https://github.com/rust-lang/crates.io-index" 2016 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 2017 | dependencies = [ 2018 | "serde", 2019 | "serde_spanned", 2020 | "toml_datetime", 2021 | "toml_edit", 2022 | ] 2023 | 2024 | [[package]] 2025 | name = "toml_datetime" 2026 | version = "0.6.8" 2027 | source = "registry+https://github.com/rust-lang/crates.io-index" 2028 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 2029 | dependencies = [ 2030 | "serde", 2031 | ] 2032 | 2033 | [[package]] 2034 | name = "toml_edit" 2035 | version = "0.22.22" 2036 | source = "registry+https://github.com/rust-lang/crates.io-index" 2037 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 2038 | dependencies = [ 2039 | "indexmap", 2040 | "serde", 2041 | "serde_spanned", 2042 | "toml_datetime", 2043 | "winnow", 2044 | ] 2045 | 2046 | [[package]] 2047 | name = "tracing" 2048 | version = "0.1.41" 2049 | source = "registry+https://github.com/rust-lang/crates.io-index" 2050 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 2051 | dependencies = [ 2052 | "log", 2053 | "pin-project-lite", 2054 | "tracing-core", 2055 | ] 2056 | 2057 | [[package]] 2058 | name = "tracing-core" 2059 | version = "0.1.33" 2060 | source = "registry+https://github.com/rust-lang/crates.io-index" 2061 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 2062 | dependencies = [ 2063 | "once_cell", 2064 | ] 2065 | 2066 | [[package]] 2067 | name = "unicode-ident" 2068 | version = "1.0.14" 2069 | source = "registry+https://github.com/rust-lang/crates.io-index" 2070 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 2071 | 2072 | [[package]] 2073 | name = "untrusted" 2074 | version = "0.9.0" 2075 | source = "registry+https://github.com/rust-lang/crates.io-index" 2076 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2077 | 2078 | [[package]] 2079 | name = "url" 2080 | version = "2.5.4" 2081 | source = "registry+https://github.com/rust-lang/crates.io-index" 2082 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 2083 | dependencies = [ 2084 | "form_urlencoded", 2085 | "idna", 2086 | "percent-encoding", 2087 | ] 2088 | 2089 | [[package]] 2090 | name = "utf16_iter" 2091 | version = "1.0.5" 2092 | source = "registry+https://github.com/rust-lang/crates.io-index" 2093 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 2094 | 2095 | [[package]] 2096 | name = "utf8_iter" 2097 | version = "1.0.4" 2098 | source = "registry+https://github.com/rust-lang/crates.io-index" 2099 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2100 | 2101 | [[package]] 2102 | name = "utf8parse" 2103 | version = "0.2.2" 2104 | source = "registry+https://github.com/rust-lang/crates.io-index" 2105 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2106 | 2107 | [[package]] 2108 | name = "version-compare" 2109 | version = "0.2.0" 2110 | source = "registry+https://github.com/rust-lang/crates.io-index" 2111 | checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" 2112 | 2113 | [[package]] 2114 | name = "walkdir" 2115 | version = "2.5.0" 2116 | source = "registry+https://github.com/rust-lang/crates.io-index" 2117 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 2118 | dependencies = [ 2119 | "same-file", 2120 | "winapi-util", 2121 | ] 2122 | 2123 | [[package]] 2124 | name = "wasi" 2125 | version = "0.11.0+wasi-snapshot-preview1" 2126 | source = "registry+https://github.com/rust-lang/crates.io-index" 2127 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2128 | 2129 | [[package]] 2130 | name = "wasm-bindgen" 2131 | version = "0.2.99" 2132 | source = "registry+https://github.com/rust-lang/crates.io-index" 2133 | checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" 2134 | dependencies = [ 2135 | "cfg-if", 2136 | "once_cell", 2137 | "wasm-bindgen-macro", 2138 | ] 2139 | 2140 | [[package]] 2141 | name = "wasm-bindgen-backend" 2142 | version = "0.2.99" 2143 | source = "registry+https://github.com/rust-lang/crates.io-index" 2144 | checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" 2145 | dependencies = [ 2146 | "bumpalo", 2147 | "log", 2148 | "proc-macro2", 2149 | "quote", 2150 | "syn", 2151 | "wasm-bindgen-shared", 2152 | ] 2153 | 2154 | [[package]] 2155 | name = "wasm-bindgen-futures" 2156 | version = "0.4.49" 2157 | source = "registry+https://github.com/rust-lang/crates.io-index" 2158 | checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" 2159 | dependencies = [ 2160 | "cfg-if", 2161 | "js-sys", 2162 | "once_cell", 2163 | "wasm-bindgen", 2164 | "web-sys", 2165 | ] 2166 | 2167 | [[package]] 2168 | name = "wasm-bindgen-macro" 2169 | version = "0.2.99" 2170 | source = "registry+https://github.com/rust-lang/crates.io-index" 2171 | checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" 2172 | dependencies = [ 2173 | "quote", 2174 | "wasm-bindgen-macro-support", 2175 | ] 2176 | 2177 | [[package]] 2178 | name = "wasm-bindgen-macro-support" 2179 | version = "0.2.99" 2180 | source = "registry+https://github.com/rust-lang/crates.io-index" 2181 | checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" 2182 | dependencies = [ 2183 | "proc-macro2", 2184 | "quote", 2185 | "syn", 2186 | "wasm-bindgen-backend", 2187 | "wasm-bindgen-shared", 2188 | ] 2189 | 2190 | [[package]] 2191 | name = "wasm-bindgen-shared" 2192 | version = "0.2.99" 2193 | source = "registry+https://github.com/rust-lang/crates.io-index" 2194 | checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" 2195 | 2196 | [[package]] 2197 | name = "web-sys" 2198 | version = "0.3.76" 2199 | source = "registry+https://github.com/rust-lang/crates.io-index" 2200 | checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" 2201 | dependencies = [ 2202 | "js-sys", 2203 | "wasm-bindgen", 2204 | ] 2205 | 2206 | [[package]] 2207 | name = "web-time" 2208 | version = "1.1.0" 2209 | source = "registry+https://github.com/rust-lang/crates.io-index" 2210 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 2211 | dependencies = [ 2212 | "js-sys", 2213 | "wasm-bindgen", 2214 | ] 2215 | 2216 | [[package]] 2217 | name = "web-transport" 2218 | version = "0.3.1" 2219 | source = "registry+https://github.com/rust-lang/crates.io-index" 2220 | checksum = "4703a5ad424f8eca7860903b94f6ed747cf58bebba3081ede78e84493a12440c" 2221 | dependencies = [ 2222 | "bytes", 2223 | "thiserror 1.0.69", 2224 | "web-transport-quinn", 2225 | "web-transport-wasm", 2226 | ] 2227 | 2228 | [[package]] 2229 | name = "web-transport-proto" 2230 | version = "0.2.3" 2231 | source = "registry+https://github.com/rust-lang/crates.io-index" 2232 | checksum = "6a3806ea43df5817f0d90618c842d28db5946bc18a5db0659b2275c2be48d472" 2233 | dependencies = [ 2234 | "bytes", 2235 | "http", 2236 | "thiserror 1.0.69", 2237 | "url", 2238 | ] 2239 | 2240 | [[package]] 2241 | name = "web-transport-quinn" 2242 | version = "0.3.4" 2243 | source = "registry+https://github.com/rust-lang/crates.io-index" 2244 | checksum = "3020b51cda10472a365e42d9a701916d4f04d74cc743de08246ef6a421c2d137" 2245 | dependencies = [ 2246 | "bytes", 2247 | "futures", 2248 | "http", 2249 | "log", 2250 | "quinn", 2251 | "quinn-proto", 2252 | "thiserror 1.0.69", 2253 | "tokio", 2254 | "url", 2255 | "web-transport-proto", 2256 | ] 2257 | 2258 | [[package]] 2259 | name = "web-transport-wasm" 2260 | version = "0.1.1" 2261 | source = "registry+https://github.com/rust-lang/crates.io-index" 2262 | checksum = "66e8f572ad133af04a5aa4a207d48d3f6a2f1f3006aa1b8f0d774d28c085d699" 2263 | dependencies = [ 2264 | "bytes", 2265 | "js-sys", 2266 | "wasm-bindgen", 2267 | "wasm-bindgen-futures", 2268 | "web-sys", 2269 | ] 2270 | 2271 | [[package]] 2272 | name = "webpki" 2273 | version = "0.22.4" 2274 | source = "registry+https://github.com/rust-lang/crates.io-index" 2275 | checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" 2276 | dependencies = [ 2277 | "ring", 2278 | "untrusted", 2279 | ] 2280 | 2281 | [[package]] 2282 | name = "webpki-root-certs" 2283 | version = "0.26.7" 2284 | source = "registry+https://github.com/rust-lang/crates.io-index" 2285 | checksum = "9cd5da49bdf1f30054cfe0b8ce2958b8fbeb67c4d82c8967a598af481bef255c" 2286 | dependencies = [ 2287 | "rustls-pki-types", 2288 | ] 2289 | 2290 | [[package]] 2291 | name = "which" 2292 | version = "4.4.2" 2293 | source = "registry+https://github.com/rust-lang/crates.io-index" 2294 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 2295 | dependencies = [ 2296 | "either", 2297 | "home", 2298 | "once_cell", 2299 | "rustix", 2300 | ] 2301 | 2302 | [[package]] 2303 | name = "winapi-util" 2304 | version = "0.1.9" 2305 | source = "registry+https://github.com/rust-lang/crates.io-index" 2306 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 2307 | dependencies = [ 2308 | "windows-sys 0.59.0", 2309 | ] 2310 | 2311 | [[package]] 2312 | name = "windows-core" 2313 | version = "0.52.0" 2314 | source = "registry+https://github.com/rust-lang/crates.io-index" 2315 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 2316 | dependencies = [ 2317 | "windows-targets", 2318 | ] 2319 | 2320 | [[package]] 2321 | name = "windows-sys" 2322 | version = "0.52.0" 2323 | source = "registry+https://github.com/rust-lang/crates.io-index" 2324 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2325 | dependencies = [ 2326 | "windows-targets", 2327 | ] 2328 | 2329 | [[package]] 2330 | name = "windows-sys" 2331 | version = "0.59.0" 2332 | source = "registry+https://github.com/rust-lang/crates.io-index" 2333 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2334 | dependencies = [ 2335 | "windows-targets", 2336 | ] 2337 | 2338 | [[package]] 2339 | name = "windows-targets" 2340 | version = "0.52.6" 2341 | source = "registry+https://github.com/rust-lang/crates.io-index" 2342 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2343 | dependencies = [ 2344 | "windows_aarch64_gnullvm", 2345 | "windows_aarch64_msvc", 2346 | "windows_i686_gnu", 2347 | "windows_i686_gnullvm", 2348 | "windows_i686_msvc", 2349 | "windows_x86_64_gnu", 2350 | "windows_x86_64_gnullvm", 2351 | "windows_x86_64_msvc", 2352 | ] 2353 | 2354 | [[package]] 2355 | name = "windows_aarch64_gnullvm" 2356 | version = "0.52.6" 2357 | source = "registry+https://github.com/rust-lang/crates.io-index" 2358 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2359 | 2360 | [[package]] 2361 | name = "windows_aarch64_msvc" 2362 | version = "0.52.6" 2363 | source = "registry+https://github.com/rust-lang/crates.io-index" 2364 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2365 | 2366 | [[package]] 2367 | name = "windows_i686_gnu" 2368 | version = "0.52.6" 2369 | source = "registry+https://github.com/rust-lang/crates.io-index" 2370 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2371 | 2372 | [[package]] 2373 | name = "windows_i686_gnullvm" 2374 | version = "0.52.6" 2375 | source = "registry+https://github.com/rust-lang/crates.io-index" 2376 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2377 | 2378 | [[package]] 2379 | name = "windows_i686_msvc" 2380 | version = "0.52.6" 2381 | source = "registry+https://github.com/rust-lang/crates.io-index" 2382 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2383 | 2384 | [[package]] 2385 | name = "windows_x86_64_gnu" 2386 | version = "0.52.6" 2387 | source = "registry+https://github.com/rust-lang/crates.io-index" 2388 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2389 | 2390 | [[package]] 2391 | name = "windows_x86_64_gnullvm" 2392 | version = "0.52.6" 2393 | source = "registry+https://github.com/rust-lang/crates.io-index" 2394 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2395 | 2396 | [[package]] 2397 | name = "windows_x86_64_msvc" 2398 | version = "0.52.6" 2399 | source = "registry+https://github.com/rust-lang/crates.io-index" 2400 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2401 | 2402 | [[package]] 2403 | name = "winnow" 2404 | version = "0.6.22" 2405 | source = "registry+https://github.com/rust-lang/crates.io-index" 2406 | checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" 2407 | dependencies = [ 2408 | "memchr", 2409 | ] 2410 | 2411 | [[package]] 2412 | name = "write16" 2413 | version = "1.0.0" 2414 | source = "registry+https://github.com/rust-lang/crates.io-index" 2415 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2416 | 2417 | [[package]] 2418 | name = "writeable" 2419 | version = "0.5.5" 2420 | source = "registry+https://github.com/rust-lang/crates.io-index" 2421 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2422 | 2423 | [[package]] 2424 | name = "yoke" 2425 | version = "0.7.5" 2426 | source = "registry+https://github.com/rust-lang/crates.io-index" 2427 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 2428 | dependencies = [ 2429 | "serde", 2430 | "stable_deref_trait", 2431 | "yoke-derive", 2432 | "zerofrom", 2433 | ] 2434 | 2435 | [[package]] 2436 | name = "yoke-derive" 2437 | version = "0.7.5" 2438 | source = "registry+https://github.com/rust-lang/crates.io-index" 2439 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 2440 | dependencies = [ 2441 | "proc-macro2", 2442 | "quote", 2443 | "syn", 2444 | "synstructure", 2445 | ] 2446 | 2447 | [[package]] 2448 | name = "zerocopy" 2449 | version = "0.7.35" 2450 | source = "registry+https://github.com/rust-lang/crates.io-index" 2451 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2452 | dependencies = [ 2453 | "byteorder", 2454 | "zerocopy-derive", 2455 | ] 2456 | 2457 | [[package]] 2458 | name = "zerocopy-derive" 2459 | version = "0.7.35" 2460 | source = "registry+https://github.com/rust-lang/crates.io-index" 2461 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2462 | dependencies = [ 2463 | "proc-macro2", 2464 | "quote", 2465 | "syn", 2466 | ] 2467 | 2468 | [[package]] 2469 | name = "zerofrom" 2470 | version = "0.1.5" 2471 | source = "registry+https://github.com/rust-lang/crates.io-index" 2472 | checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" 2473 | dependencies = [ 2474 | "zerofrom-derive", 2475 | ] 2476 | 2477 | [[package]] 2478 | name = "zerofrom-derive" 2479 | version = "0.1.5" 2480 | source = "registry+https://github.com/rust-lang/crates.io-index" 2481 | checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" 2482 | dependencies = [ 2483 | "proc-macro2", 2484 | "quote", 2485 | "syn", 2486 | "synstructure", 2487 | ] 2488 | 2489 | [[package]] 2490 | name = "zeroize" 2491 | version = "1.8.1" 2492 | source = "registry+https://github.com/rust-lang/crates.io-index" 2493 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2494 | 2495 | [[package]] 2496 | name = "zerovec" 2497 | version = "0.10.4" 2498 | source = "registry+https://github.com/rust-lang/crates.io-index" 2499 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2500 | dependencies = [ 2501 | "yoke", 2502 | "zerofrom", 2503 | "zerovec-derive", 2504 | ] 2505 | 2506 | [[package]] 2507 | name = "zerovec-derive" 2508 | version = "0.10.3" 2509 | source = "registry+https://github.com/rust-lang/crates.io-index" 2510 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2511 | dependencies = [ 2512 | "proc-macro2", 2513 | "quote", 2514 | "syn", 2515 | ] 2516 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gst-moq" 3 | authors = ["Rafael Caricio "] 4 | version = "0.1.0" 5 | edition = "2021" 6 | license = "MPL-2.0" 7 | publish = ["crates-io"] 8 | 9 | [dependencies] 10 | gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" } 11 | gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" } 12 | gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" } 13 | gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main", features = ["v1_22"] } 14 | gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" } 15 | gst-pbutils = { package = "gstreamer-pbutils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main", features = ["v1_20"] } 16 | tokio = { version = "1.36.0", default-features = false, features = ["time", "rt-multi-thread", "macros"] } 17 | futures = "0.3.30" 18 | quinn = { version = "0.11.5", default-features = false, features = ["ring", "rustls", "runtime-tokio", "log"] } 19 | quinn-proto ={ version = "0.11.8", default-features = false, features = ["rustls", "log"] } 20 | rustls = { version = "0.23", default-features = false, features = ["std"] } 21 | rustls-pemfile = "2" 22 | rustls-pki-types = "1" 23 | bytes = "1.5.0" 24 | thiserror = "2" 25 | web-transport-quinn = "0.3.3" 26 | url = "2.5.2" 27 | once_cell = "1.20.2" 28 | http = "1.1" 29 | moq-transport = { git = "https://github.com/englishm/moq-rs.git", rev = "ebc843de8504e37d36c3134a1181513ebdf7a34a" } 30 | moq-catalog = { git = "https://github.com/englishm/moq-rs.git", rev = "ebc843de8504e37d36c3134a1181513ebdf7a34a" } 31 | moq-native-ietf = { git = "https://github.com/englishm/moq-rs.git", rev = "ebc843de8504e37d36c3134a1181513ebdf7a34a" } 32 | serde_json = "1.0" 33 | anyhow = "1.0" 34 | 35 | [dev-dependencies] 36 | clap = { version = "4", features = ["derive"] } 37 | ctrlc = "3.4" 38 | env_logger = "0.11" 39 | 40 | [profile.release] 41 | lto = "thin" 42 | opt-level = 3 43 | debug = true 44 | panic = "unwind" 45 | 46 | [profile.dev] 47 | opt-level = 1 48 | lto = "off" 49 | 50 | [lib] 51 | name = "gstmoq" 52 | crate-type = ["cdylib", "rlib"] 53 | path = "src/lib.rs" 54 | 55 | [build-dependencies] 56 | gst-plugin-version-helper = "0.8.1" 57 | 58 | [features] 59 | static = [] 60 | capi = [] 61 | doc = [] 62 | 63 | [package.metadata.capi] 64 | min_version = "0.9.21" 65 | 66 | [package.metadata.capi.header] 67 | enabled = false 68 | 69 | [package.metadata.capi.library] 70 | install_subdir = "gstreamer-1.0" 71 | versioning = false 72 | import_library = false 73 | 74 | [package.metadata.capi.pkg_config] 75 | requires_private = "gstreamer-1.0, gstreamer-base-1.0, gobject-2.0, glib-2.0" 76 | 77 | [[example]] 78 | name = "videopub" 79 | path = "examples/videopub.rs" 80 | required-features = [] 81 | 82 | [[example]] 83 | name = "filepub" 84 | path = "examples/filepub.rs" 85 | required-features = [] 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GStreamer MoQ Publisher Plugin 2 | 3 | A GStreamer plugin that implements a Media over QUIC (MoQ) publisher element. This element allows publishing live media streams to MoQ relay servers using the WebTransport protocol. 4 | 5 | ## Overview 6 | 7 | The `moqpublisher` element is a bin element that accepts video and/or audio input streams and publishes them to a MoQ relay server. It handles: 8 | 9 | - WebTransport connection management 10 | - CMAF packaging of media streams 11 | - MoQ catalog generation and updates 12 | - Track management and prioritization 13 | - Init segment handling 14 | 15 | ## Installation 16 | 17 | ### Prerequisites 18 | 19 | - Rust toolchain (latest stable version) 20 | - GStreamer development files (>= 1.22) 21 | - pkg-config 22 | 23 | ### Building 24 | 25 | ```bash 26 | cargo build --release 27 | ``` 28 | 29 | ## Element Properties 30 | 31 | - `url` (string): The WebTransport URL of the MoQ relay server 32 | - `namespace` (string): The namespace for the published tracks (default: "default") 33 | - `fragment-duration` (uint64): Duration of each media fragment in milliseconds (default: 2000) 34 | - `certificate-file` (string): Optional path to TLS certificate file for the WebTransport connection 35 | 36 | ## Pad Properties 37 | 38 | Each sink pad supports the following properties via a "track-settings" structure: 39 | 40 | - `track-name` (string): Name of the track in the MoQ catalog 41 | - `priority` (uint8): Priority value for the track's fragments (0-255, default: 127) 42 | 43 | ## Internal Architecture 44 | 45 | The element works by: 46 | 47 | 1. Creating a bin containing cmafmux and appsink elements for each input pad 48 | 2. Using cmafmux to package incoming media into CMAF fragments 49 | 3. Processing CMAF fragments into MoQ groups and objects 50 | 4. Managing WebTransport connections to the relay server 51 | 5. Generating and updating the MoQ catalog based on media capabilities 52 | 6. Publishing media tracks with proper prioritization 53 | 54 | ## Example Usage 55 | 56 | The repository includes a video publishing example that can be run with: 57 | 58 | ```bash 59 | cargo run --example videopub -- \ 60 | --url https://localhost:4443 \ 61 | --namespace example \ 62 | --fragment-duration 2000 \ 63 | --with-audio # Optional: enable audio publishing 64 | ``` 65 | 66 | ### Example Pipeline 67 | 68 | ```bash 69 | gst-launch-1.0 \ 70 | videotestsrc ! x264enc ! h264parse ! \ 71 | moqpublisher url="https://localhost:4443" namespace="example" \ 72 | fragment-duration=2000 73 | ``` 74 | 75 | ### Building a Custom Pipeline 76 | 77 | The element supports multiple input pads for different media tracks. Each pad must be requested with `request_pad_simple("sink_%u")` and configured with appropriate track settings: 78 | 79 | ```rust 80 | let moqpub = gst::ElementFactory::make("moqpublisher") 81 | .property("url", "https://localhost:4443") 82 | .property("namespace", "example") 83 | .build()?; 84 | 85 | // Request and configure video pad 86 | let video_pad = moqpub.request_pad_simple("sink_%u").unwrap(); 87 | let video_settings = gst::Structure::builder("video1-rendition") 88 | .field("track-name", "video_1") 89 | .field("priority", 127u8) 90 | .build(); 91 | video_pad.set_property("track-settings", &video_settings); 92 | ``` 93 | 94 | ## Debugging 95 | 96 | The element supports GStreamer's debug system. Enable debug output with: 97 | 98 | ```bash 99 | export GST_DEBUG=moqpublisher:4 100 | ``` 101 | 102 | The example program also generates pipeline graphs in dot format when running. These can be converted to images with: 103 | 104 | ```bash 105 | dot -Tpng moq-publisher.dot > pipeline.png 106 | ``` 107 | 108 | ## License 109 | 110 | This project is licensed under the Mozilla Public License 2.0 - see the LICENSE file for details. 111 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | gst_plugin_version_helper::info() 3 | } 4 | -------------------------------------------------------------------------------- /examples/filepub.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use gst::glib; 3 | use gst::prelude::*; 4 | use std::sync::{Arc, Mutex}; 5 | 6 | /// MoQ video publisher example that publishes from a file source 7 | #[derive(Parser, Debug)] 8 | #[command(author, version, about, long_about = None)] 9 | struct Args { 10 | /// Source file path 11 | #[arg(short, long)] 12 | source: String, 13 | 14 | /// MoQ relay URL 15 | #[arg(short, long, default_value = "https://localhost:4443")] 16 | url: String, 17 | 18 | /// MoQ namespace 19 | #[arg(short, long, default_value = "example")] 20 | namespace: String, 21 | 22 | /// Fragment duration in milliseconds 23 | #[arg(short, long, default_value_t = 2000)] 24 | fragment_duration: u64, 25 | 26 | /// Optional client certificate file 27 | #[arg(short, long)] 28 | certificate_file: Option, 29 | 30 | /// Skip audio from source file 31 | #[arg(long)] 32 | no_audio: bool, 33 | } 34 | 35 | fn create_video_encoder() -> Result<(gst::Element, gst::Element), Box> { 36 | let videoenc = gst::ElementFactory::make("x264enc") 37 | .property("bframes", 0u32) 38 | .property("key-int-max", 50u32) 39 | .property_from_str("tune", "zerolatency") 40 | .build()?; 41 | 42 | let h264_capsfilter = gst::ElementFactory::make("capsfilter") 43 | .property( 44 | "caps", 45 | gst::Caps::builder("video/x-h264") 46 | .field("profile", "main") 47 | .field("framerate", &gst::Fraction::new(25, 1)) 48 | .build(), 49 | ) 50 | .build()?; 51 | 52 | Ok((videoenc, h264_capsfilter)) 53 | } 54 | 55 | fn create_audio_encoder() -> Result<(gst::Element, gst::Element), Box> { 56 | let audioenc = gst::ElementFactory::make("avenc_aac").build()?; 57 | let audio_capsfilter = gst::ElementFactory::make("capsfilter") 58 | .property( 59 | "caps", 60 | gst::Caps::builder("audio/mpeg") 61 | .field("mpegversion", 4i32) 62 | .build(), 63 | ) 64 | .build()?; 65 | 66 | Ok((audioenc, audio_capsfilter)) 67 | } 68 | 69 | fn setup_source_pipeline( 70 | pipeline: &gst::Pipeline, 71 | moqpub: &gst::Element, 72 | source_path: &str, 73 | no_audio: bool, 74 | ) -> Result<(), Box> { 75 | // Create source elements 76 | let filesrc = gst::ElementFactory::make("filesrc") 77 | .property("location", source_path) 78 | .build()?; 79 | let decodebin = gst::ElementFactory::make("decodebin").build()?; 80 | 81 | // Add elements to pipeline 82 | pipeline.add_many([&filesrc, &decodebin])?; 83 | gst::Element::link_many(&[&filesrc, &decodebin])?; 84 | 85 | // Create converters and encoders, but don't link them yet 86 | let videoconvert = gst::ElementFactory::make("videoconvert").build()?; 87 | let videorate = gst::ElementFactory::make("videorate").build()?; 88 | let (videoenc, h264_capsfilter) = create_video_encoder()?; 89 | pipeline.add_many([&videoconvert, &videorate, &videoenc, &h264_capsfilter])?; 90 | gst::Element::link_many(&[&videoconvert, &videorate, &videoenc, &h264_capsfilter])?; 91 | 92 | // Setup video pad on moqpub 93 | let video_pad = moqpub.request_pad_simple("sink_%u").unwrap(); 94 | let video_settings = gst::Structure::builder("video1-rendition") 95 | .field("track-name", "video_1") 96 | .field("priority", 127u8) 97 | .build(); 98 | video_pad.set_property("track-settings", &video_settings); 99 | 100 | let video_src_pad = h264_capsfilter.static_pad("src").unwrap(); 101 | video_src_pad.link(&video_pad)?; 102 | 103 | // Create audio elements if audio is enabled 104 | let audio_elements: Option<(gst::Element, gst::Element, gst::Element, gst::Element)> = if !no_audio { 105 | let audioconvert = gst::ElementFactory::make("audioconvert").build()?; 106 | let audioresample = gst::ElementFactory::make("audioresample").build()?; 107 | let (audioenc, audio_capsfilter) = create_audio_encoder()?; 108 | 109 | pipeline.add_many([&audioconvert, &audioresample, &audioenc, &audio_capsfilter])?; 110 | gst::Element::link_many(&[ 111 | &audioconvert, 112 | &audioresample, 113 | &audioenc, 114 | &audio_capsfilter, 115 | ])?; 116 | 117 | let audio_pad = moqpub.request_pad_simple("sink_%u").unwrap(); 118 | let audio_settings = gst::Structure::builder("audio1-rendition") 119 | .field("track-name", "audio_1") 120 | .field("priority", 100u8) 121 | .build(); 122 | audio_pad.set_property("track-settings", &audio_settings); 123 | 124 | let audio_src_pad = audio_capsfilter.static_pad("src").unwrap(); 125 | audio_src_pad.link(&audio_pad)?; 126 | 127 | Some((audioconvert, audioresample, audioenc, audio_capsfilter)) 128 | } else { 129 | None 130 | }; 131 | 132 | // Handle pad-added signal from decodebin 133 | decodebin.connect_pad_added(move |dbin, src_pad| { 134 | println!("Pad added on decodebin"); 135 | 136 | let caps = src_pad.current_caps().unwrap(); 137 | let str_caps = caps.structure(0).unwrap(); 138 | let media_type = str_caps.name(); 139 | 140 | match media_type.as_str() { 141 | "video/x-raw" => { 142 | let sink_pad = videoconvert.static_pad("sink").unwrap(); 143 | if src_pad.link(&sink_pad).is_err() { 144 | eprintln!("Failed to link video pad"); 145 | } 146 | } 147 | "audio/x-raw" => { 148 | if !no_audio { 149 | if let Some((audioconvert, _, _, _)) = &audio_elements 150 | { 151 | let sink_pad = audioconvert.static_pad("sink").unwrap(); 152 | if src_pad.link(&sink_pad).is_err() { 153 | eprintln!("Failed to link audio pad"); 154 | } 155 | } 156 | } 157 | } 158 | _ => { 159 | println!("Unknown pad type: {}", media_type); 160 | } 161 | } 162 | }); 163 | 164 | Ok(()) 165 | } 166 | 167 | fn main() -> Result<(), Box> { 168 | env_logger::init(); 169 | 170 | // Parse command line arguments 171 | let args = Args::parse(); 172 | 173 | // Initialize GStreamer 174 | gst::init()?; 175 | gstmoq::plugin_register_static().unwrap(); 176 | 177 | // Create pipeline 178 | let pipeline = gst::Pipeline::new(); 179 | 180 | // Create moqpublisher 181 | let moqpub = gst::ElementFactory::make("moqpublisher") 182 | .property("url", args.url) 183 | .property("namespace", args.namespace) 184 | .property("fragment-duration", args.fragment_duration.mseconds()) 185 | .property("chunk-duration", 40.mseconds()) // 25fps thus 40ms per frame 186 | .build()?; 187 | 188 | // Add certificate file if specified 189 | if let Some(cert_file) = args.certificate_file { 190 | moqpub.set_property("certificate-file", cert_file); 191 | } 192 | 193 | pipeline.add(&moqpub)?; 194 | 195 | // Setup the source pipeline 196 | setup_source_pipeline(&pipeline, &moqpub, &args.source, args.no_audio)?; 197 | 198 | // Create a main loop to run our GStreamer pipeline 199 | let main_loop = glib::MainLoop::new(None, false); 200 | let main_loop_clone = main_loop.clone(); 201 | 202 | // Create an error flag that we can share between callbacks 203 | let error_occurred = Arc::new(Mutex::new(false)); 204 | let error_occurred_clone = error_occurred.clone(); 205 | 206 | // Add a bus watch to handle messages from the pipeline 207 | let bus = pipeline.bus().unwrap(); 208 | let _watch = bus.add_watch({ 209 | let pipeline_weak = pipeline.downgrade(); 210 | move |_, msg| { 211 | let pipeline = match pipeline_weak.upgrade() { 212 | Some(pipeline) => pipeline, 213 | None => return glib::ControlFlow::Break, 214 | }; 215 | use gst::MessageView; 216 | 217 | match msg.view() { 218 | MessageView::Eos(..) => { 219 | println!("End of stream received"); 220 | main_loop_clone.quit(); 221 | return glib::ControlFlow::Break; 222 | } 223 | MessageView::Error(err) => { 224 | let mut error_flag = error_occurred_clone.lock().unwrap(); 225 | *error_flag = true; 226 | eprintln!( 227 | "Error from {:?}: {} ({:?})", 228 | err.src().map(|s| s.path_string()), 229 | err.error(), 230 | err.debug() 231 | ); 232 | main_loop_clone.quit(); 233 | return glib::ControlFlow::Break; 234 | } 235 | MessageView::StateChanged(state) => { 236 | // Only print state changes from the pipeline 237 | if state 238 | .src() 239 | .map(|s| s == pipeline.upcast_ref::()) 240 | .unwrap_or(false) 241 | { 242 | println!( 243 | "Pipeline state changed from {:?} to {:?}", 244 | state.old(), 245 | state.current() 246 | ); 247 | } 248 | } 249 | _ => (), 250 | } 251 | glib::ControlFlow::Continue 252 | } 253 | })?; 254 | 255 | // Setup signal handling for Ctrl+C 256 | ctrlc::set_handler({ 257 | let main_loop = main_loop.clone(); 258 | let pipeline = pipeline.downgrade(); 259 | move || { 260 | println!("\nReceived interrupt signal, stopping pipeline..."); 261 | let pipeline = match pipeline.upgrade() { 262 | Some(pipeline) => pipeline, 263 | None => { 264 | main_loop.quit(); 265 | return; 266 | } 267 | }; 268 | pipeline 269 | .debug_to_dot_file_with_ts(gst::DebugGraphDetails::all(), "moq-publisher-quitting"); 270 | pipeline.send_event(gst::event::Eos::new()); 271 | main_loop.quit(); 272 | } 273 | })?; 274 | 275 | // Start playing 276 | pipeline.set_state(gst::State::Playing)?; 277 | println!("Pipeline is playing..."); 278 | 279 | let weak_pipeline = pipeline.downgrade(); 280 | glib::timeout_add_seconds_once(5, move || { 281 | let pipeline = match weak_pipeline.upgrade() { 282 | Some(pipeline) => pipeline, 283 | None => return, 284 | }; 285 | 286 | pipeline.debug_to_dot_file_with_ts(gst::DebugGraphDetails::all(), "moq-publisher"); 287 | }); 288 | 289 | // Run the main loop 290 | main_loop.run(); 291 | 292 | // Cleanup 293 | println!("Stopping pipeline..."); 294 | pipeline.set_state(gst::State::Null)?; 295 | 296 | // Check if we're exiting due to an error 297 | if *error_occurred.lock().unwrap() { 298 | std::process::exit(1); 299 | } 300 | 301 | Ok(()) 302 | } 303 | -------------------------------------------------------------------------------- /examples/videopub.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use gst::glib; 3 | use gst::prelude::*; 4 | use std::sync::{Arc, Mutex}; 5 | 6 | /// Simple MoQ video publisher example 7 | #[derive(Parser, Debug)] 8 | #[command(author, version, about, long_about = None)] 9 | struct Args { 10 | /// MoQ relay URL 11 | #[arg(short, long, default_value = "https://localhost:4443")] 12 | url: String, 13 | 14 | /// MoQ namespace 15 | #[arg(short, long, default_value = "example")] 16 | namespace: String, 17 | 18 | /// Fragment duration in milliseconds 19 | #[arg(short, long, default_value_t = 2000)] 20 | fragment_duration: u64, 21 | 22 | /// Optional client certificate file 23 | #[arg(short, long)] 24 | certificate_file: Option, 25 | 26 | /// Enable audio publishing 27 | #[arg(long)] 28 | with_audio: bool, 29 | } 30 | 31 | fn setup_audio_elements( 32 | pipeline: &gst::Pipeline, 33 | moqpub: &gst::Element, 34 | ) -> Result<(), Box> { 35 | let audiotestsrc = gst::ElementFactory::make("audiotestsrc") 36 | .property_from_str("wave", "ticks") 37 | .build()?; 38 | 39 | let audioenc = gst::ElementFactory::make("avenc_aac").build()?; 40 | 41 | let audio_capsfilter = gst::ElementFactory::make("capsfilter") 42 | .property( 43 | "caps", 44 | gst::Caps::builder("audio/mpeg") 45 | .field("mpegversion", 4i32) 46 | .build(), 47 | ) 48 | .build()?; 49 | 50 | pipeline.add_many([&audiotestsrc, &audioenc, &audio_capsfilter])?; 51 | gst::Element::link_many(&[&audiotestsrc, &audioenc, &audio_capsfilter])?; 52 | 53 | let audio_pad = moqpub.request_pad_simple("sink_%u").unwrap(); 54 | let audio_settings = gst::Structure::builder("audio1-rendition") 55 | .field("track-name", "audio_1") 56 | .field("priority", 100u8) 57 | .build(); 58 | audio_pad.set_property("track-settings", &audio_settings); 59 | 60 | let audio_src_pad = audio_capsfilter.static_pad("src").unwrap(); 61 | audio_src_pad.link(&audio_pad)?; 62 | 63 | Ok(()) 64 | } 65 | 66 | fn main() -> Result<(), Box> { 67 | env_logger::init(); 68 | // Parse command line arguments 69 | let args = Args::parse(); 70 | 71 | // Initialize GStreamer 72 | gst::init()?; 73 | gstmoq::plugin_register_static().unwrap(); 74 | 75 | // Create pipeline 76 | let pipeline = gst::Pipeline::new(); 77 | 78 | // Create moqpublisher 79 | let moqpub = gst::ElementFactory::make("moqpublisher") 80 | .property("url", args.url) 81 | .property("namespace", args.namespace) 82 | .property("fragment-duration", args.fragment_duration.mseconds()) 83 | .property("chunk-duration", 40.mseconds()) // 25fps thus 40ms per frame 84 | .build()?; 85 | 86 | // Add certificate file if specified 87 | if let Some(cert_file) = args.certificate_file { 88 | moqpub.set_property("certificate-file", cert_file); 89 | } 90 | 91 | // Create video source elements 92 | let videotestsrc = gst::ElementFactory::make("videotestsrc") 93 | .property_from_str("pattern", "ball") 94 | .build()?; 95 | 96 | let videoenc = gst::ElementFactory::make("x264enc") 97 | .property("bframes", 0u32) 98 | .property("key-int-max", 50u32) // We want keyframes every 2 seconds, as we use chunk-duration, the fragment-duration (request IDR frames) is not used 99 | .property_from_str("tune", "zerolatency") 100 | .build()?; 101 | 102 | let h264_capsfilter = gst::ElementFactory::make("capsfilter") 103 | .property( 104 | "caps", 105 | gst::Caps::builder("video/x-h264") 106 | .field("profile", "main") 107 | .field("framerate", &gst::Fraction::new(25, 1)) 108 | .build(), 109 | ) 110 | .build()?; 111 | 112 | // Add elements to pipeline 113 | pipeline.add_many([&videotestsrc, &videoenc, &h264_capsfilter, &moqpub])?; 114 | 115 | // Link video elements 116 | gst::Element::link_many(&[&videotestsrc, &videoenc, &h264_capsfilter])?; 117 | 118 | // Setup moqpublisher pad 119 | let video_pad = moqpub.request_pad_simple("sink_%u").unwrap(); 120 | let s = gst::Structure::builder("video1-rendition") 121 | .field("track-name", "video_1") 122 | .field("priority", 127u8) 123 | .build(); 124 | video_pad.set_property("track-settings", &s); 125 | 126 | let caps_src_pad = h264_capsfilter.static_pad("src").unwrap(); 127 | caps_src_pad.link(&video_pad)?; 128 | 129 | // After creating moqpublisher, add: 130 | if args.with_audio { 131 | setup_audio_elements(&pipeline, &moqpub)?; 132 | } 133 | 134 | // Create a main loop to run our GStreamer pipeline 135 | let main_loop = glib::MainLoop::new(None, false); 136 | let main_loop_clone = main_loop.clone(); 137 | 138 | // Create an error flag that we can share between callbacks 139 | let error_occurred = Arc::new(Mutex::new(false)); 140 | let error_occurred_clone = error_occurred.clone(); 141 | 142 | // Add a bus watch to handle messages from the pipeline 143 | let bus = pipeline.bus().unwrap(); 144 | let _watch = bus.add_watch({ 145 | let pipeline_weak = pipeline.downgrade(); 146 | move |_, msg| { 147 | let pipeline = match pipeline_weak.upgrade() { 148 | Some(pipeline) => pipeline, 149 | None => return glib::ControlFlow::Break, 150 | }; 151 | use gst::MessageView; 152 | 153 | match msg.view() { 154 | MessageView::Eos(..) => { 155 | println!("End of stream received"); 156 | main_loop_clone.quit(); 157 | return glib::ControlFlow::Break; 158 | } 159 | MessageView::Error(err) => { 160 | let mut error_flag = error_occurred_clone.lock().unwrap(); 161 | *error_flag = true; 162 | eprintln!( 163 | "Error from {:?}: {} ({:?})", 164 | err.src().map(|s| s.path_string()), 165 | err.error(), 166 | err.debug() 167 | ); 168 | main_loop_clone.quit(); 169 | return glib::ControlFlow::Break; 170 | } 171 | MessageView::StateChanged(state) => { 172 | // Only print state changes from the pipeline 173 | if state 174 | .src() 175 | .map(|s| s == pipeline.upcast_ref::()) 176 | .unwrap_or(false) 177 | { 178 | println!( 179 | "Pipeline state changed from {:?} to {:?}", 180 | state.old(), 181 | state.current() 182 | ); 183 | } 184 | } 185 | _ => (), 186 | } 187 | glib::ControlFlow::Continue 188 | } 189 | })?; 190 | 191 | // Setup signal handling for Ctrl+C 192 | ctrlc::set_handler({ 193 | let main_loop = main_loop.clone(); 194 | let pipeline = pipeline.downgrade(); 195 | move || { 196 | println!("\nReceived interrupt signal, stopping pipeline..."); 197 | let pipeline = match pipeline.upgrade() { 198 | Some(pipeline) => pipeline, 199 | None => { 200 | main_loop.quit(); 201 | return; 202 | } 203 | }; 204 | pipeline 205 | .debug_to_dot_file_with_ts(gst::DebugGraphDetails::all(), "moq-publisher-quitting"); 206 | pipeline.send_event(gst::event::Eos::new()); 207 | main_loop.quit(); 208 | } 209 | })?; 210 | 211 | // Start playing 212 | pipeline.set_state(gst::State::Playing)?; 213 | println!("Pipeline is playing..."); 214 | 215 | let weak_pipeline = pipeline.downgrade(); 216 | glib::timeout_add_seconds_once(5, move || { 217 | let pipeline = match weak_pipeline.upgrade() { 218 | Some(pipeline) => pipeline, 219 | None => return, 220 | }; 221 | 222 | pipeline.debug_to_dot_file_with_ts(gst::DebugGraphDetails::all(), "moq-publisher"); 223 | }); 224 | 225 | // Run the main loop 226 | main_loop.run(); 227 | 228 | // Cleanup 229 | println!("Stopping pipeline..."); 230 | pipeline.set_state(gst::State::Null)?; 231 | 232 | // Check if we're exiting due to an error 233 | if *error_occurred.lock().unwrap() { 234 | std::process::exit(1); 235 | } 236 | 237 | Ok(()) 238 | } 239 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod moqpublisher; 2 | 3 | use gst::glib; 4 | 5 | fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { 6 | moqpublisher::register(plugin)?; 7 | 8 | Ok(()) 9 | } 10 | 11 | gst::plugin_define!( 12 | gstmoq, 13 | env!("CARGO_PKG_DESCRIPTION"), 14 | plugin_init, 15 | concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")), 16 | "MPL", 17 | env!("CARGO_PKG_NAME"), 18 | env!("CARGO_PKG_NAME"), 19 | env!("CARGO_PKG_REPOSITORY"), 20 | env!("BUILD_REL_DATE") 21 | ); 22 | -------------------------------------------------------------------------------- /src/moqpublisher/imp.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025, Rafael Caricio 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. 4 | // If a copy of the MPL was not distributed with this file, You can obtain one at 5 | // . 6 | // 7 | // SPDX-License-Identifier: MPL-2.0 8 | 9 | use anyhow::Error; 10 | use bytes::Bytes; 11 | use gst::glib; 12 | use gst::prelude::*; 13 | use gst::subclass::prelude::*; 14 | use moq_transport::coding::Tuple; 15 | use moq_transport::serve; 16 | use moq_transport::session::Publisher; 17 | use once_cell::sync::Lazy; 18 | use std::collections::HashMap; 19 | use std::path::PathBuf; 20 | use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; 21 | use std::sync::LazyLock; 22 | use std::sync::{Arc, Mutex}; 23 | use tokio::runtime; 24 | use tokio::task::JoinHandle; 25 | use url; 26 | 27 | static CAT: Lazy = Lazy::new(|| { 28 | gst::DebugCategory::new( 29 | "moqpublisher", 30 | gst::DebugColorFlags::empty(), 31 | Some("MoQ Publisher Element"), 32 | ) 33 | }); 34 | 35 | static RUNTIME: LazyLock = LazyLock::new(|| { 36 | runtime::Builder::new_multi_thread() 37 | .enable_all() 38 | .worker_threads(1) 39 | .build() 40 | .unwrap() 41 | }); 42 | 43 | #[derive(Default, Clone)] 44 | struct MoqPublisherSinkPadSettings { 45 | // MoQ track name for this pad 46 | track_name: Option, 47 | 48 | // Track priority (0-255) 49 | priority: Option, 50 | } 51 | 52 | impl From for MoqPublisherSinkPadSettings { 53 | fn from(s: gst::Structure) -> Self { 54 | MoqPublisherSinkPadSettings { 55 | track_name: s.get_optional::("track-name").unwrap(), 56 | priority: s.get_optional::("priority").unwrap(), 57 | } 58 | } 59 | } 60 | 61 | impl From for gst::Structure { 62 | fn from(obj: MoqPublisherSinkPadSettings) -> Self { 63 | gst::Structure::builder("track-settings") 64 | .field_if_some("track-name", obj.track_name) 65 | .field_if_some("priority", obj.priority) 66 | .build() 67 | } 68 | } 69 | 70 | #[derive(Default)] 71 | pub(crate) struct MoqPublisherSinkPad { 72 | settings: Mutex, 73 | } 74 | 75 | #[glib::object_subclass] 76 | impl ObjectSubclass for MoqPublisherSinkPad { 77 | const NAME: &'static str = "MoqPublisherSinkPad"; 78 | type Type = super::MoqPublisherSinkPad; 79 | type ParentType = gst::GhostPad; 80 | } 81 | 82 | impl ObjectImpl for MoqPublisherSinkPad { 83 | fn properties() -> &'static [glib::ParamSpec] { 84 | static PROPERTIES: Lazy> = Lazy::new(|| { 85 | vec![ 86 | glib::ParamSpecBoxed::builder::("track-settings") 87 | .nick("Track Settings") 88 | .blurb("MoQ track settings") 89 | .mutable_ready() 90 | .build(), 91 | ] 92 | }); 93 | 94 | PROPERTIES.as_ref() 95 | } 96 | 97 | fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { 98 | match pspec.name() { 99 | "track-settings" => { 100 | let s = value 101 | .get::() 102 | .expect("Must be a valid structure"); 103 | let mut settings = self.settings.lock().unwrap(); 104 | *settings = s.into(); 105 | } 106 | _ => unimplemented!(), 107 | } 108 | } 109 | 110 | fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { 111 | let settings = self.settings.lock().unwrap(); 112 | 113 | match pspec.name() { 114 | "track-settings" => Into::::into(settings.clone()).to_value(), 115 | _ => unimplemented!(), 116 | } 117 | } 118 | } 119 | 120 | impl GstObjectImpl for MoqPublisherSinkPad {} 121 | 122 | impl PadImpl for MoqPublisherSinkPad {} 123 | 124 | impl ProxyPadImpl for MoqPublisherSinkPad {} 125 | 126 | impl GhostPadImpl for MoqPublisherSinkPad {} 127 | 128 | struct State { 129 | broadcast: serve::TracksWriter, 130 | catalog: serve::SubgroupsWriter, 131 | connection_task: JoinHandle<()>, 132 | announce_task: JoinHandle<()>, 133 | } 134 | 135 | static DEFAULT_URL: &str = ""; 136 | static DEFAULT_CERTIFICATE_FILE: Option = None; 137 | static DEFAULT_FRAGMENT_DURATION: u64 = 2000; 138 | static DEFAULT_CHUNK_DURATION: u64 = i64::MAX as u64; 139 | static DEFAULT_NAMESPACE: &str = "default"; 140 | 141 | #[derive(Debug, Clone)] 142 | struct Settings { 143 | // MoQ relay URL to connect to using WebTransport 144 | url: String, 145 | 146 | // Path to client certificate file for WebTransport 147 | certificate_file: Option, 148 | 149 | // Makes sure all cmafmux GoP's duration are the same 150 | fragment_duration: u64, 151 | 152 | // Duration of each chunk 153 | chunk_duration: u64, 154 | 155 | // MoQ namespace for tracks 156 | namespace: String, 157 | } 158 | 159 | impl Default for Settings { 160 | fn default() -> Self { 161 | Self { 162 | url: DEFAULT_URL.to_string(), 163 | certificate_file: DEFAULT_CERTIFICATE_FILE.clone(), 164 | fragment_duration: DEFAULT_FRAGMENT_DURATION, 165 | chunk_duration: DEFAULT_CHUNK_DURATION, 166 | namespace: DEFAULT_NAMESPACE.to_string(), 167 | } 168 | } 169 | } 170 | 171 | struct TrackData { 172 | segment_track_name: Option, 173 | segment_track: Option, 174 | init_track: Option, 175 | init_track_name: Option, 176 | pad: String, 177 | sequence: AtomicU64, 178 | 179 | // Media info for track, set after the caps are fixed 180 | media_info: Option, 181 | } 182 | 183 | #[derive(Clone)] 184 | struct MediaInfo { 185 | codec_mime: String, 186 | width: Option, 187 | height: Option, 188 | channels: Option, 189 | sample_rate: Option, 190 | } 191 | 192 | fn get_codec_mime_from_caps(caps: &gst::CapsRef) -> String { 193 | let mime = gst_pbutils::codec_utils_caps_get_mime_codec(caps); 194 | mime.map(|s| s.to_string()) 195 | .unwrap_or_else(|_| "application/octet-stream".to_string()) 196 | } 197 | 198 | impl MediaInfo { 199 | fn new(caps: &gst::CapsRef) -> Self { 200 | let codec_mime = get_codec_mime_from_caps(caps); 201 | let s = caps.structure(0).unwrap(); 202 | let width = s.get::("width").ok(); 203 | let height = s.get::("height").ok(); 204 | let channels = s.get::("channels").ok(); 205 | let sample_rate = s.get::("rate").ok(); 206 | 207 | Self { 208 | codec_mime, 209 | width, 210 | height, 211 | channels, 212 | sample_rate, 213 | } 214 | } 215 | } 216 | 217 | pub struct MoqPublisher { 218 | settings: Arc>, 219 | state: Arc>>, 220 | 221 | // Pad name -> TrackData 222 | track_data: Mutex>, 223 | 224 | // Counter for generating unique sink pad names 225 | sink_pad_counter: AtomicU32, 226 | } 227 | 228 | #[glib::object_subclass] 229 | impl ObjectSubclass for MoqPublisher { 230 | const NAME: &'static str = "GstMoqPublisher"; 231 | type Type = super::MoqPublisher; 232 | type ParentType = gst::Bin; 233 | type Interfaces = (gst::ChildProxy,); 234 | 235 | fn new() -> Self { 236 | Self { 237 | settings: Arc::new(Mutex::new(Settings::default())), 238 | state: Arc::new(Mutex::new(None)), 239 | track_data: Mutex::new(HashMap::new()), 240 | sink_pad_counter: AtomicU32::new(0), 241 | } 242 | } 243 | } 244 | 245 | impl GstObjectImpl for MoqPublisher {} 246 | 247 | impl BinImpl for MoqPublisher {} 248 | 249 | impl ObjectImpl for MoqPublisher { 250 | fn properties() -> &'static [glib::ParamSpec] { 251 | static PROPERTIES: Lazy> = Lazy::new(|| { 252 | vec![ 253 | glib::ParamSpecString::builder("url") 254 | .nick("URL") 255 | .blurb("MoQ relay URL") 256 | .build(), 257 | glib::ParamSpecString::builder("certificate-file") 258 | .nick("Certificate File") 259 | .blurb("Path to client certificate file") 260 | .build(), 261 | glib::ParamSpecUInt64::builder("fragment-duration") 262 | .nick("Fragment Duration") 263 | .blurb("CMAF fragment duration in milliseconds") 264 | .default_value(DEFAULT_FRAGMENT_DURATION) 265 | .build(), 266 | glib::ParamSpecUInt64::builder("chunk-duration") 267 | .nick("Chunk Duration") 268 | .blurb("Duration of each chunk in milliseconds") 269 | .default_value(DEFAULT_CHUNK_DURATION) 270 | .build(), 271 | glib::ParamSpecString::builder("namespace") 272 | .nick("Namespace") 273 | .blurb("MoQ namespace for tracks") 274 | .default_value("default") 275 | .build(), 276 | ] 277 | }); 278 | PROPERTIES.as_ref() 279 | } 280 | 281 | fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { 282 | let mut settings = self.settings.lock().unwrap(); 283 | match pspec.name() { 284 | "url" => { 285 | settings.url = value.get::().unwrap(); 286 | } 287 | "certificate-file" => { 288 | if let Ok(path) = value.get::() { 289 | settings.certificate_file = Some(PathBuf::from(path)); 290 | } else { 291 | settings.certificate_file = None; 292 | } 293 | } 294 | "fragment-duration" => { 295 | settings.fragment_duration = value.get::().unwrap(); 296 | } 297 | "chunk-duration" => { 298 | settings.chunk_duration = value.get::().unwrap(); 299 | } 300 | "namespace" => { 301 | settings.namespace = value.get::().unwrap(); 302 | } 303 | _ => unimplemented!(), 304 | } 305 | } 306 | 307 | fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { 308 | let settings = self.settings.lock().unwrap(); 309 | match pspec.name() { 310 | "url" => settings.url.to_value(), 311 | "certificate-file" => settings 312 | .certificate_file 313 | .as_ref() 314 | .map(|p| p.to_str().unwrap().to_string()) 315 | .to_value(), 316 | "fragment-duration" => settings.fragment_duration.to_value(), 317 | "chunk-duration" => settings.chunk_duration.to_value(), 318 | "namespace" => settings.namespace.to_value(), 319 | _ => unimplemented!(), 320 | } 321 | } 322 | } 323 | 324 | impl ChildProxyImpl for MoqPublisher { 325 | fn children_count(&self) -> u32 { 326 | let object = self.obj(); 327 | object.num_pads() as u32 328 | } 329 | 330 | fn child_by_name(&self, name: &str) -> Option { 331 | let object = self.obj(); 332 | object 333 | .pads() 334 | .into_iter() 335 | .find(|p| p.name() == name) 336 | .map(|p| p.upcast()) 337 | } 338 | 339 | fn child_by_index(&self, index: u32) -> Option { 340 | let object = self.obj(); 341 | object 342 | .pads() 343 | .into_iter() 344 | .nth(index as usize) 345 | .map(|p| p.upcast()) 346 | } 347 | } 348 | 349 | impl ElementImpl for MoqPublisher { 350 | fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { 351 | static ELEMENT_METADATA: LazyLock = LazyLock::new(|| { 352 | gst::subclass::ElementMetadata::new( 353 | "MoQ Publisher Sink", 354 | "Source/Network/QUIC/MoQ", 355 | "Send media data to a MoQ relay server", 356 | "Rafael Caricio ", 357 | ) 358 | }); 359 | Some(&*ELEMENT_METADATA) 360 | } 361 | 362 | fn pad_templates() -> &'static [gst::PadTemplate] { 363 | static PAD_TEMPLATES: LazyLock> = LazyLock::new(|| { 364 | // Caps are restricted by the cmafmux element negotiation inside our bin element 365 | let sink_pad_template = gst::PadTemplate::with_gtype( 366 | "sink_%u", 367 | gst::PadDirection::Sink, 368 | gst::PadPresence::Request, 369 | &gst::Caps::new_any(), 370 | super::MoqPublisherSinkPad::static_type(), 371 | ) 372 | .unwrap(); 373 | 374 | vec![sink_pad_template] 375 | }); 376 | 377 | PAD_TEMPLATES.as_ref() 378 | } 379 | 380 | fn request_new_pad( 381 | &self, 382 | templ: &gst::PadTemplate, 383 | _name: Option<&str>, 384 | _caps: Option<&gst::Caps>, 385 | ) -> Option { 386 | let element = self.obj(); 387 | 388 | // Only allow pad requests in NULL/READY state 389 | if element.current_state() > gst::State::Ready { 390 | gst::error!( 391 | CAT, 392 | obj = element, 393 | "Cannot request pad in non-NULL/READY state" 394 | ); 395 | return None; 396 | } 397 | 398 | gst::info!(CAT, obj = element, "Requesting new sink pad"); 399 | 400 | let settings = self.settings.lock().unwrap(); 401 | 402 | // Generate unique pad name 403 | let pad_num = self.sink_pad_counter.fetch_add(1, Ordering::SeqCst); 404 | let pad_name = format!("sink_{}", pad_num); 405 | 406 | // Create the sink pad 407 | let sink_pad = gst::PadBuilder::::from_template(templ) 408 | .name(&pad_name) 409 | .build(); 410 | 411 | // Create internal elements 412 | let cmafmux = gst::ElementFactory::make("cmafmux") 413 | .property("fragment-duration", settings.fragment_duration) 414 | .property("chunk-duration", settings.chunk_duration) 415 | .property_from_str("header-update-mode", "update") 416 | .property("write-mehd", true) 417 | .build() 418 | .unwrap(); 419 | 420 | let appsink = gst_app::AppSink::builder() 421 | .name(pad_name.clone()) 422 | .buffer_list(true) 423 | .sync(true) 424 | .callbacks({ 425 | let this = self.downgrade(); 426 | 427 | gst_app::AppSinkCallbacks::builder() 428 | .new_sample(move |appsink| { 429 | let this = match this.upgrade() { 430 | Some(this) => this, 431 | None => return Ok(gst::FlowSuccess::Ok), 432 | }; 433 | 434 | let sample = appsink.pull_sample().map_err(|_| gst::FlowError::Eos)?; 435 | let buffer_list = sample.buffer_list_owned().expect("no buffer list"); 436 | 437 | let mut track_data = this.track_data.lock().unwrap(); 438 | let track = track_data.get_mut(&pad_name).unwrap(); 439 | 440 | gst::debug!( 441 | CAT, 442 | obj = this.obj(), 443 | "Received buffer list with {} buffers", 444 | buffer_list.len() 445 | ); 446 | 447 | if let Err(err) = this.handle_cmaf_data(track, buffer_list) { 448 | gst::error!(CAT, "Failed to handle CMAF data: {}", err); 449 | return Err(gst::FlowError::Error); 450 | } 451 | 452 | Ok(gst::FlowSuccess::Ok) 453 | }) 454 | .build() 455 | }) 456 | .build(); 457 | 458 | // Add elements to bin 459 | element.add_many([&cmafmux, appsink.upcast_ref()]).unwrap(); 460 | gst::Element::link_many([&cmafmux, appsink.upcast_ref()]).unwrap(); 461 | 462 | // Create ghost pad 463 | let muxer_sink = cmafmux.static_pad("sink").unwrap(); 464 | sink_pad.set_target(Some(&muxer_sink)).unwrap(); 465 | 466 | // Add probe for caps events 467 | sink_pad.add_probe(gst::PadProbeType::EVENT_BOTH, { 468 | let this = self.downgrade(); 469 | move |pad, info| { 470 | let this = match this.upgrade() { 471 | Some(this) => this, 472 | None => return gst::PadProbeReturn::Remove, 473 | }; 474 | 475 | if let Some(gst::PadProbeData::Event(ref event)) = info.data { 476 | if let gst::EventView::Caps(caps_evt) = event.view() { 477 | let caps = caps_evt.caps(); 478 | gst::debug!( 479 | CAT, 480 | obj = this.obj(), 481 | "Received caps event on pad {}: {:?}", 482 | pad.name(), 483 | caps 484 | ); 485 | 486 | // Create MediaInfo from the caps 487 | let media_info = MediaInfo::new(caps); 488 | 489 | // Store the media info in track data 490 | let mut track_data = this.track_data.lock().unwrap(); 491 | gst::debug!( 492 | CAT, 493 | obj = this.obj(), 494 | "Updating media info for track {}", 495 | pad.name() 496 | ); 497 | if let Some(track) = track_data.get_mut(&pad.name().to_string()) { 498 | track.media_info = Some(media_info); 499 | gst::debug!( 500 | CAT, 501 | obj = this.obj(), 502 | "Updated media info for track {}", 503 | pad.name() 504 | ); 505 | 506 | // Check if all tracks have media info and create catalog if ready 507 | if track_data.values().all(|t| t.media_info.is_some()) { 508 | gst::debug!( 509 | CAT, 510 | obj = this.obj(), 511 | "All tracks have media info, creating catalog" 512 | ); 513 | drop(track_data); 514 | if let Err(e) = this.create_catalog() { 515 | gst::error!(CAT, obj = pad, "Failed to create catalog: {}", e); 516 | } 517 | } else { 518 | gst::debug!( 519 | CAT, 520 | obj = this.obj(), 521 | "Not all tracks have media info yet" 522 | ); 523 | } 524 | } 525 | } 526 | } 527 | 528 | gst::PadProbeReturn::Ok 529 | } 530 | }); 531 | 532 | // Add pad to element 533 | element.add_pad(&sink_pad).unwrap(); 534 | element.child_added(sink_pad.upcast_ref::(), &sink_pad.name()); 535 | 536 | // Store track data 537 | let track_data = TrackData { 538 | segment_track: None, // Will be set up in READY->PAUSED 539 | segment_track_name: None, 540 | init_track: None, // Will be set up in READY->PAUSED 541 | init_track_name: None, 542 | pad: sink_pad.name().to_string(), 543 | sequence: AtomicU64::new(1), // Start at 1 since 0 is reserved for init segment 544 | media_info: None, 545 | }; 546 | 547 | self.track_data 548 | .lock() 549 | .unwrap() 550 | .insert(sink_pad.name().to_string(), track_data); 551 | 552 | Some(sink_pad.upcast()) 553 | } 554 | 555 | fn change_state( 556 | &self, 557 | transition: gst::StateChange, 558 | ) -> Result { 559 | match transition { 560 | gst::StateChange::ReadyToPaused => { 561 | // Setup connection and publisher in one go 562 | if let Err(e) = self.setup_connection() { 563 | gst::error!(CAT, obj = self.obj(), "Failed to setup connection: {:?}", e); 564 | return Err(e); 565 | } 566 | 567 | // Announce tracks after connection setup 568 | if let Err(e) = self.announce_tracks() { 569 | gst::error!(CAT, obj = self.obj(), "Failed to announce tracks: {:?}", e); 570 | return Err(e); 571 | } 572 | } 573 | 574 | gst::StateChange::PausedToReady => { 575 | // Cleanup publisher 576 | self.cleanup_publisher(); 577 | } 578 | 579 | _ => (), 580 | } 581 | 582 | // Chain up 583 | self.parent_change_state(transition) 584 | } 585 | } 586 | 587 | impl MoqPublisher { 588 | fn setup_connection(&self) -> Result<(), gst::StateChangeError> { 589 | let settings = self.settings.lock().unwrap(); 590 | let url = url::Url::parse(&settings.url).map_err(|e| { 591 | gst::error!(CAT, obj = self.obj(), "Invalid URL {}: {}", settings.url, e); 592 | gst::StateChangeError 593 | })?; 594 | let namespace = Tuple::from_utf8_path(&settings.namespace.clone()); 595 | let cert_path = settings.certificate_file.clone(); 596 | drop(settings); 597 | 598 | // Setup connection synchronously in the runtime 599 | let (broadcast, catalog, connection_task, announce_task) = RUNTIME.block_on(async { 600 | let mut tls = moq_native_ietf::tls::Args::default(); 601 | if let Some(path) = cert_path { 602 | tls.root = vec![path]; 603 | } 604 | tls.disable_verify = true; 605 | 606 | let quic = moq_native_ietf::quic::Endpoint::new(moq_native_ietf::quic::Config { 607 | bind: "[::]:0".parse().unwrap(), 608 | tls: tls.load().map_err(|e| { 609 | gst::error!(CAT, obj = self.obj(), "Failed to load TLS config: {}", e); 610 | gst::StateChangeError 611 | })?, 612 | }) 613 | .map_err(|e| { 614 | gst::error!( 615 | CAT, 616 | obj = self.obj(), 617 | "Failed to create QUIC endpoint: {}", 618 | e 619 | ); 620 | gst::StateChangeError 621 | })?; 622 | 623 | let session = quic.client.connect(&url).await.map_err(|e| { 624 | gst::error!(CAT, obj = self.obj(), "Failed to connect: {}", e); 625 | gst::StateChangeError 626 | })?; 627 | 628 | let (session, mut publisher) = Publisher::connect(session).await.map_err(|e| { 629 | gst::error!(CAT, obj = self.obj(), "Failed to create publisher: {}", e); 630 | gst::StateChangeError 631 | })?; 632 | 633 | // Create tracks container 634 | let (mut broadcast, _, reader) = serve::Tracks::new(namespace).produce(); 635 | 636 | // Create catalog track 637 | let catalog = broadcast 638 | .create(".catalog") 639 | .ok_or_else(|| gst::StateChangeError)? 640 | .groups() 641 | .map_err(|_| gst::StateChangeError)?; 642 | 643 | // Create connection task 644 | let connection_task = tokio::spawn({ 645 | let el_weak = self.obj().downgrade(); 646 | async move { 647 | let element = el_weak.upgrade().unwrap(); 648 | gst::debug!(CAT, obj = element, "Starting session"); 649 | if let Err(e) = session.run().await { 650 | gst::error!(CAT, obj = element, "Session error: {}", e); 651 | element.send_event(gst::event::Eos::new()); 652 | } 653 | gst::debug!(CAT, obj = element, "Session done!"); 654 | } 655 | }); 656 | 657 | // Create announce task 658 | let announce_task = tokio::spawn({ 659 | let el_weak = self.obj().downgrade(); 660 | async move { 661 | let element = el_weak.upgrade().unwrap(); 662 | gst::debug!(CAT, obj = element, "Announcing tracks"); 663 | if let Err(e) = publisher.announce(reader).await { 664 | gst::error!(CAT, obj = element, "Publisher announce error: {}", e); 665 | element.send_event(gst::event::Eos::new()); 666 | } 667 | gst::debug!(CAT, obj = element, "Announce done!"); 668 | } 669 | }); 670 | 671 | Ok::<_, gst::StateChangeError>((broadcast, catalog, connection_task, announce_task)) 672 | })?; 673 | 674 | gst::info!(CAT, obj = self.obj(), "Connected to MoQ relay at {}", url); 675 | 676 | // Store new state 677 | *self.state.lock().unwrap() = Some(State { 678 | broadcast, 679 | catalog, 680 | connection_task, 681 | announce_task, 682 | }); 683 | 684 | Ok(()) 685 | } 686 | 687 | fn announce_tracks(&self) -> Result<(), gst::StateChangeError> { 688 | let mut state_guard = self.state.lock().unwrap(); 689 | let state = state_guard.as_mut().ok_or_else(|| { 690 | gst::error!(CAT, obj = self.obj(), "Not in ready state"); 691 | gst::StateChangeError 692 | })?; 693 | 694 | let mut track_data = self.track_data.lock().unwrap(); 695 | 696 | for (pad_name, track) in track_data.iter_mut() { 697 | let pad = self 698 | .obj() 699 | .static_pad(pad_name) 700 | .unwrap() 701 | .downcast::() 702 | .unwrap(); 703 | 704 | let track_name = pad 705 | .imp() 706 | .settings 707 | .lock() 708 | .unwrap() 709 | .track_name 710 | .clone() 711 | .unwrap_or_else(|| pad_name.clone()); 712 | let init_track_name = format!("{}_init.mp4", track_name); 713 | let segment_track_name = format!("{}.m4s", track_name); 714 | 715 | // Create per-track init segment track 716 | let init_track = state 717 | .broadcast 718 | .create(&init_track_name) 719 | .ok_or_else(|| { 720 | gst::error!(CAT, obj = self.obj(), "Failed to create init track"); 721 | gst::StateChangeError 722 | })? 723 | .groups() 724 | .map_err(|_| gst::StateChangeError)?; 725 | 726 | track.init_track = Some(init_track); 727 | 728 | // Create media track 729 | let track_writer = state.broadcast.create(&segment_track_name).ok_or_else(|| { 730 | gst::error!(CAT, obj = self.obj(), "Failed to create track writer"); 731 | gst::StateChangeError 732 | })?; 733 | 734 | track.init_track_name = Some(init_track_name); 735 | track.segment_track_name = Some(segment_track_name); 736 | 737 | track.segment_track = Some(track_writer.groups().map_err(|_| { 738 | gst::error!(CAT, obj = self.obj(), "Failed to create segment track"); 739 | gst::StateChangeError 740 | })?); 741 | } 742 | 743 | Ok(()) 744 | } 745 | 746 | fn create_catalog(&self) -> Result<(), Error> { 747 | let mut state_guard = self.state.lock().unwrap(); 748 | let state = state_guard 749 | .as_mut() 750 | .ok_or_else(|| anyhow::anyhow!("Not in ready state"))?; 751 | 752 | let mut tracks = Vec::new(); 753 | let mut track_data = self.track_data.lock().unwrap(); 754 | 755 | for (pad_name, track) in track_data.iter_mut() { 756 | let media_info = track.media_info.as_ref().ok_or_else(|| { 757 | anyhow::anyhow!("Missing media info for track on pad {}", pad_name) 758 | })?; 759 | 760 | let mut selection_params = moq_catalog::SelectionParam::default(); 761 | 762 | // Use get_mime_type for codec string 763 | selection_params.codec = Some(media_info.codec_mime.clone()); 764 | 765 | // Set video-specific parameters 766 | if let (Some(width), Some(height)) = (media_info.width, media_info.height) { 767 | selection_params.width = Some(width as u32); 768 | selection_params.height = Some(height as u32); 769 | } 770 | 771 | // Set audio-specific parameters 772 | if let Some(channels) = media_info.channels { 773 | selection_params.channel_config = Some(channels.to_string()); 774 | } 775 | if let Some(sample_rate) = media_info.sample_rate { 776 | selection_params.samplerate = Some(sample_rate as u32); 777 | } 778 | 779 | let catalog_track = moq_catalog::Track { 780 | init_track: track.init_track_name.clone(), 781 | name: track.segment_track_name.clone().unwrap(), 782 | namespace: Some(state.broadcast.namespace.to_utf8_path()), 783 | packaging: Some(moq_catalog::TrackPackaging::Cmaf), 784 | render_group: Some(1), 785 | selection_params, 786 | ..Default::default() 787 | }; 788 | 789 | tracks.push(catalog_track); 790 | } 791 | 792 | let catalog = moq_catalog::Root { 793 | version: 1, 794 | streaming_format: 1, 795 | streaming_format_version: "0.2".to_string(), 796 | streaming_delta_updates: true, 797 | common_track_fields: moq_catalog::CommonTrackFields::from_tracks(&mut tracks), 798 | tracks, 799 | }; 800 | 801 | let catalog_str = serde_json::to_string_pretty(&catalog)?; 802 | gst::info!(CAT, obj = self.obj(), "MoQ catalog: {}", catalog_str); 803 | 804 | // Write catalog to track 805 | state 806 | .catalog 807 | .append(0) 808 | .map_err(|e| { 809 | gst::error!(CAT, obj = self.obj(), "Failed to append catalog: {}", e); 810 | gst::StateChangeError 811 | })? 812 | .write(catalog_str.into()) 813 | .map_err(|e| { 814 | gst::error!(CAT, obj = self.obj(), "Failed to write catalog: {}", e); 815 | gst::StateChangeError 816 | })?; 817 | 818 | gst::debug!(CAT, obj = self.obj(), "Published catalog"); 819 | 820 | Ok(()) 821 | } 822 | 823 | fn cleanup_publisher(&self) { 824 | let mut state = self.state.lock().unwrap(); 825 | 826 | // Clear track writers 827 | let mut track_data = self.track_data.lock().unwrap(); 828 | for (_, track) in track_data.iter_mut() { 829 | track.segment_track = None; 830 | } 831 | 832 | // Take and clean up state if it exists 833 | if let Some(state) = state.take() { 834 | // Abort both tasks 835 | state.connection_task.abort(); 836 | state.announce_task.abort(); 837 | 838 | // Wait for both tasks 839 | let _ = RUNTIME.block_on(async { 840 | let _ = state.connection_task.await; 841 | let _ = state.announce_task.await; 842 | }); 843 | } 844 | } 845 | 846 | fn handle_cmaf_data( 847 | &self, 848 | track: &mut TrackData, 849 | mut buffer_list: gst::BufferList, 850 | ) -> Result<(), Error> { 851 | assert!(!buffer_list.is_empty()); 852 | 853 | let mut first = buffer_list.get(0).unwrap(); 854 | 855 | // Handle initialization segment 856 | if first 857 | .flags() 858 | .contains(gst::BufferFlags::DISCONT | gst::BufferFlags::HEADER) 859 | { 860 | // Write to track's init track 861 | let init_track = track.init_track.as_mut().ok_or_else(|| { 862 | anyhow::anyhow!("Init track not initialized for pad {}", track.pad) 863 | })?; 864 | 865 | let mut group = init_track.append(0)?; 866 | let map = first.map_readable().unwrap(); 867 | group.write(Bytes::copy_from_slice(&map))?; 868 | drop(map); 869 | 870 | gst::debug!( 871 | CAT, 872 | obj = self.obj(), 873 | "Wrote init segment for track {}", 874 | track.pad 875 | ); 876 | 877 | // Remove init segment from buffer list and continue with media data 878 | buffer_list.make_mut().remove(0..1); 879 | 880 | if buffer_list.is_empty() { 881 | return Ok(()); 882 | } 883 | first = buffer_list.get(0).unwrap(); 884 | } 885 | 886 | // Process the rest of the media segment 887 | if !first.flags().contains(gst::BufferFlags::HEADER) { 888 | return Err(anyhow::anyhow!("Missing segment header")); 889 | } 890 | 891 | let pad = self 892 | .obj() 893 | .static_pad(&track.pad) 894 | .unwrap() 895 | .downcast::() 896 | .unwrap(); 897 | 898 | let segment_sequence = track.sequence.fetch_add(1, Ordering::SeqCst); 899 | let priority = pad.imp().settings.lock().unwrap().priority; 900 | 901 | // Create MoQ group for segment 902 | let mut group = track 903 | .segment_track 904 | .as_mut() 905 | .expect("track writer not initialized") 906 | .create(serve::Subgroup { 907 | group_id: segment_sequence, 908 | priority: priority.unwrap_or(127), 909 | subgroup_id: 0, 910 | })?; 911 | 912 | // Write all buffers 913 | for buffer in buffer_list.iter() { 914 | let map = buffer.map_readable().unwrap(); 915 | group.write(Bytes::copy_from_slice(&map))?; 916 | } 917 | 918 | gst::debug!( 919 | CAT, 920 | obj = self.obj(), 921 | "Published group {} for pad {}", 922 | segment_sequence, 923 | track.pad 924 | ); 925 | 926 | Ok(()) 927 | } 928 | } 929 | -------------------------------------------------------------------------------- /src/moqpublisher/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025, Rafael Caricio 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. 4 | // If a copy of the MPL was not distributed with this file, You can obtain one at 5 | // . 6 | // 7 | // SPDX-License-Identifier: MPL-2.0 8 | 9 | use gst::glib; 10 | use gst::prelude::*; 11 | 12 | mod imp; 13 | 14 | glib::wrapper! { 15 | pub struct MoqPublisher(ObjectSubclass) @extends gst::Bin, gst::Element, gst::Object, @implements gst::ChildProxy; 16 | } 17 | 18 | glib::wrapper! { 19 | pub(crate) struct MoqPublisherSinkPad(ObjectSubclass) @extends gst::GhostPad, gst::ProxyPad, gst::Pad, gst::Object; 20 | } 21 | 22 | pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { 23 | gst::Element::register( 24 | Some(plugin), 25 | "moqpublisher", 26 | gst::Rank::NONE, 27 | MoqPublisher::static_type(), 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /tests/moqpublisher.rs: -------------------------------------------------------------------------------- 1 | use gst::prelude::*; 2 | 3 | fn init() { 4 | use std::sync::Once; 5 | static INIT: Once = Once::new(); 6 | 7 | INIT.call_once(|| { 8 | gst::init().unwrap(); 9 | gstmoq::plugin_register_static().unwrap(); 10 | }); 11 | } 12 | 13 | #[test] 14 | fn test_element_basic_usage() { 15 | init(); 16 | 17 | // Create test pipeline 18 | let pipeline = gst::Pipeline::new(); 19 | 20 | // Create moqpublisher 21 | let moqpub = gst::ElementFactory::make("moqpublisher") 22 | .property("url", "https://localhost:4443") 23 | .property("namespace", "testmovie") 24 | .property("fragment-duration", 2000.mseconds()) 25 | .build() 26 | .unwrap(); 27 | 28 | // Create video source elements 29 | let videotestsrc = gst::ElementFactory::make("videotestsrc") 30 | .property("pattern", "ball") 31 | .build() 32 | .unwrap(); 33 | let videoenc = gst::ElementFactory::make("x264enc") 34 | .property("bframes", 0u32) 35 | .property("key-int-max", i32::MAX as u32) // Let the muxer drive keyframe generation 36 | .property("tune", "zerolatency") 37 | .build() 38 | .unwrap(); 39 | let h264_capsfilter = gst::ElementFactory::make("capsfilter") 40 | .property( 41 | "caps", 42 | gst::Caps::builder("video/x-h264") 43 | .field("profile", "main") 44 | .build(), 45 | ) 46 | .build() 47 | .unwrap(); 48 | 49 | // Add elements to pipeline 50 | pipeline 51 | .add_many([&videotestsrc, &videoenc, &h264_capsfilter, &moqpub]) 52 | .unwrap(); 53 | 54 | // Link video elements 55 | gst::Element::link_many(&[&videotestsrc, &videoenc, &h264_capsfilter]).unwrap(); 56 | 57 | // setup moqpublisher pad 58 | let video_pad = moqpub.request_pad_simple("sink_0").unwrap(); 59 | let s = gst::Structure::builder("video1-rendition") 60 | .field("track-name", "VIDEO") 61 | .field("priority", 127u8) 62 | .build(); 63 | video_pad.set_property("track-settings", &s); 64 | 65 | let caps_src_pad = h264_capsfilter.static_pad("src").unwrap(); 66 | caps_src_pad.link(&video_pad).unwrap(); 67 | 68 | // Test state changes 69 | pipeline.set_state(gst::State::Paused).unwrap(); 70 | 71 | // Wait for preroll 72 | let _ = pipeline.state(gst::ClockTime::from_seconds(5)); 73 | 74 | // Cleanup 75 | pipeline.set_state(gst::State::Null).unwrap(); 76 | } 77 | -------------------------------------------------------------------------------- /work_plan.md: -------------------------------------------------------------------------------- 1 | 2 | Issues to be fixed in the `moqpublisher` element code: 3 | - Missing to keep the "broadcast" (with name broadcast to avoid confusion) in the element state 4 | - Missing to create the init for every track with name "0.mp4" eg. `let init = broadcast.create("0.mp4")` 5 | - Missing creating the ".catalog" track 6 | - Missing to populate the catalog track with the tracks information as `moq_catalog::Root` object. Eg.: 7 | ```rust 8 | let catalog = moq_catalog::Root { 9 | version: 1, 10 | streaming_format: 1, 11 | streaming_format_version: "0.2".to_string(), 12 | streaming_delta_updates: true, 13 | common_track_fields: moq_catalog::CommonTrackFields::from_tracks(&mut tracks), 14 | tracks, 15 | }; 16 | 17 | let catalog_str = serde_json::to_string_pretty(&catalog)?; 18 | log::info!("catalog: {}", catalog_str); 19 | 20 | // Create a single fragment for the segment. 21 | self.catalog.append(0)?.write(catalog_str.into())?; 22 | ``` 23 | - Missing to create a `moq_catalog::Track` for each track 24 | - Missing to populate `let mut selection_params = moq_catalog::SelectionParam::default();` for each track 25 | 26 | 27 | We have example code for all the above in the moq-pub project. We can use to learn how to apply that to our GStreamer element. --------------------------------------------------------------------------------