parent
5ba457bf65
commit
2601c40b54
|
@ -1 +1 @@
|
||||||
logs/
|
target
|
||||||
|
|
|
@ -0,0 +1,279 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cmake"
|
||||||
|
version = "0.1.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.61"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.141"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "octets"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "git+https://github.com/n8o/quiche.git?branch=master#0137dc3ca6f4f31e3175d0a0868acb9c64b46cc7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.17.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.56"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quiche"
|
||||||
|
version = "0.17.1"
|
||||||
|
source = "git+https://github.com/n8o/quiche.git?branch=master#0137dc3ca6f4f31e3175d0a0868acb9c64b46cc7"
|
||||||
|
dependencies = [
|
||||||
|
"cmake",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"libm",
|
||||||
|
"log",
|
||||||
|
"octets",
|
||||||
|
"ring",
|
||||||
|
"slab",
|
||||||
|
"smallvec",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.16.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"spin",
|
||||||
|
"untrusted",
|
||||||
|
"web-sys",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.160"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "warp-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"quiche",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.84"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.84"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.84"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.84"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.84"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.61"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "warp-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
quiche = { git = "https://github.com/n8o/quiche.git", branch = "master" } # WebTransport fork
|
|
@ -1,30 +0,0 @@
|
||||||
module github.com/kixelated/warp/server
|
|
||||||
|
|
||||||
go 1.18
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/abema/go-mp4 v0.7.2
|
|
||||||
github.com/kixelated/invoker v1.0.0
|
|
||||||
github.com/kixelated/quic-go v1.31.0
|
|
||||||
github.com/kixelated/webtransport-go v1.4.1
|
|
||||||
github.com/zencoder/go-dash/v3 v3.0.2
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
|
||||||
github.com/golang/mock v1.6.0 // indirect
|
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
|
||||||
github.com/google/uuid v1.1.2 // indirect
|
|
||||||
github.com/marten-seemann/qpack v0.3.0 // indirect
|
|
||||||
github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
|
|
||||||
github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
|
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
|
||||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
|
||||||
golang.org/x/sys v0.1.1-0.20221102194838-fc697a31fa06 // indirect
|
|
||||||
golang.org/x/text v0.3.7 // indirect
|
|
||||||
golang.org/x/tools v0.1.12 // indirect
|
|
||||||
)
|
|
264
server/go.sum
264
server/go.sum
|
@ -1,264 +0,0 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
|
||||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
|
||||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
|
||||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
|
||||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
|
||||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/abema/go-mp4 v0.7.2 h1:ugTC8gfEmjyaDKpXs3vi2QzgJbDu9B8m6UMMIpbYbGg=
|
|
||||||
github.com/abema/go-mp4 v0.7.2/go.mod h1:vPl9t5ZK7K0x68jh12/+ECWBCXoWuIDtNgPtU2f04ws=
|
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
|
||||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
|
||||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
|
||||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
|
||||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
|
||||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
|
||||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
|
||||||
github.com/kisielk/errcheck v1.4.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/kixelated/invoker v1.0.0 h1:0wYlvK39yQPbkwIFy+YN41AhF89WOtGyWqV2pZB39xw=
|
|
||||||
github.com/kixelated/invoker v1.0.0/go.mod h1:RjG3iqm/sKwZjOpcW4SGq+l+4DJCDR/yUtc70VjCRB8=
|
|
||||||
github.com/kixelated/quic-go v1.31.0 h1:p2vq3Otvtmz+0EP23vjumnO/HU4Q/DFxNF6xNryVfmA=
|
|
||||||
github.com/kixelated/quic-go v1.31.0/go.mod h1:AO7pURnb8HXHmdalp5e09UxQfsuwseEhl0NLmwiSOFY=
|
|
||||||
github.com/kixelated/webtransport-go v1.4.1 h1:ZtY3P7hVe1wK5fAt71b+HHnNISFDcQ913v+bvaNATxA=
|
|
||||||
github.com/kixelated/webtransport-go v1.4.1/go.mod h1:6RV5pTXF7oP53T83bosSDsLdSdw31j5cfpMDqsO4D5k=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
|
||||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
|
||||||
github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE=
|
|
||||||
github.com/marten-seemann/qpack v0.3.0/go.mod h1:cGfKPBiP4a9EQdxCwEwI/GEeWAsjSekBvx/X8mh58+g=
|
|
||||||
github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI=
|
|
||||||
github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
|
||||||
github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE=
|
|
||||||
github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
|
||||||
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
|
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
|
||||||
github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e h1:s2RNOM/IGdY0Y6qfTeUKhDawdHDpK9RGBdx80qN4Ttw=
|
|
||||||
github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e/go.mod h1:nBdnFKj15wFbf94Rwfq4m30eAcyY9V/IyKAGQFtqkW0=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
|
||||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
|
||||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
|
||||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
|
||||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
|
||||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
|
||||||
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
|
||||||
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
|
||||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
|
||||||
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
|
||||||
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
|
||||||
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
|
||||||
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
|
||||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
|
||||||
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
|
||||||
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
|
||||||
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
|
||||||
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
|
||||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
|
||||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
|
||||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
|
||||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
|
||||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
||||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
|
||||||
github.com/sunfish-shogi/bufseekio v0.0.0-20210207115823-a4185644b365/go.mod h1:dEzdXgvImkQ3WLI+0KQpmEx8T/C/ma9KeS3AfmU899I=
|
|
||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
|
||||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
|
||||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
|
||||||
github.com/zencoder/go-dash/v3 v3.0.2 h1:oP1+dOh+Gp57PkvdCyMfbHtrHaxfl3w4kR3KBBbuqQE=
|
|
||||||
github.com/zencoder/go-dash/v3 v3.0.2/go.mod h1:30R5bKy1aUYY45yesjtZ9l8trNc2TwNqbS17WVQmCzk=
|
|
||||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
|
||||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
|
||||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
|
||||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
|
|
||||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.1.1-0.20221102194838-fc697a31fa06 h1:E1pm64FqQa4v8dHd/bAneyMkR4hk8LTJhoSlc5mc1cM=
|
|
||||||
golang.org/x/sys v0.1.1-0.20221102194838-fc697a31fa06/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
|
||||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
|
||||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
|
||||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
|
||||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
|
||||||
gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
|
|
||||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY=
|
|
||||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
|
||||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
|
|
@ -1,380 +0,0 @@
|
||||||
package warp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/abema/go-mp4"
|
|
||||||
"github.com/kixelated/invoker"
|
|
||||||
"github.com/zencoder/go-dash/v3/mpd"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is a demo; you should actually fetch media from a live backend.
|
|
||||||
// It's just much easier to read from disk and "fake" being live.
|
|
||||||
type Media struct {
|
|
||||||
base fs.FS
|
|
||||||
inits map[string]*MediaInit
|
|
||||||
video []*mpd.Representation
|
|
||||||
audio []*mpd.Representation
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMedia(playlistPath string) (m *Media, err error) {
|
|
||||||
m = new(Media)
|
|
||||||
|
|
||||||
// Create a fs.FS out of the folder holding the playlist
|
|
||||||
m.base = os.DirFS(filepath.Dir(playlistPath))
|
|
||||||
|
|
||||||
// Read the playlist file
|
|
||||||
playlist, err := mpd.ReadFromFile(playlistPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to open playlist: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(playlist.Periods) > 1 {
|
|
||||||
return nil, fmt.Errorf("multiple periods not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
period := playlist.Periods[0]
|
|
||||||
|
|
||||||
for _, adaption := range period.AdaptationSets {
|
|
||||||
representation := adaption.Representations[0]
|
|
||||||
|
|
||||||
if representation.MimeType == nil {
|
|
||||||
return nil, fmt.Errorf("missing representation mime type")
|
|
||||||
}
|
|
||||||
|
|
||||||
if representation.Bandwidth == nil {
|
|
||||||
return nil, fmt.Errorf("missing representation bandwidth")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch *representation.MimeType {
|
|
||||||
case "video/mp4":
|
|
||||||
m.video = append(m.video, representation)
|
|
||||||
case "audio/mp4":
|
|
||||||
m.audio = append(m.audio, representation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(m.video) == 0 {
|
|
||||||
return nil, fmt.Errorf("no video representation found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(m.audio) == 0 {
|
|
||||||
return nil, fmt.Errorf("no audio representation found")
|
|
||||||
}
|
|
||||||
|
|
||||||
m.inits = make(map[string]*MediaInit)
|
|
||||||
|
|
||||||
var reps []*mpd.Representation
|
|
||||||
reps = append(reps, m.audio...)
|
|
||||||
reps = append(reps, m.video...)
|
|
||||||
|
|
||||||
for _, rep := range reps {
|
|
||||||
path := *rep.SegmentTemplate.Initialization
|
|
||||||
|
|
||||||
// TODO Support the full template engine
|
|
||||||
path = strings.ReplaceAll(path, "$RepresentationID$", *rep.ID)
|
|
||||||
|
|
||||||
f, err := fs.ReadFile(m.base, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read init file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
init, err := newMediaInit(*rep.ID, f)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create init segment: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.inits[*rep.ID] = init
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Media) Start(bitrate func() uint64) (inits map[string]*MediaInit, audio *MediaStream, video *MediaStream, err error) {
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
audio, err = newMediaStream(m, m.audio, start, bitrate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
video, err = newMediaStream(m, m.video, start, bitrate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.inits, audio, video, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MediaStream struct {
|
|
||||||
Media *Media
|
|
||||||
|
|
||||||
start time.Time
|
|
||||||
reps []*mpd.Representation
|
|
||||||
sequence int
|
|
||||||
bitrate func() uint64 // returns the current estimated bitrate
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMediaStream(m *Media, reps []*mpd.Representation, start time.Time, bitrate func() uint64) (ms *MediaStream, err error) {
|
|
||||||
ms = new(MediaStream)
|
|
||||||
ms.Media = m
|
|
||||||
ms.reps = reps
|
|
||||||
ms.start = start
|
|
||||||
ms.bitrate = bitrate
|
|
||||||
return ms, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms *MediaStream) chooseRepresentation() (choice *mpd.Representation) {
|
|
||||||
bitrate := ms.bitrate()
|
|
||||||
|
|
||||||
// Loop over the renditions and pick the highest bitrate we can support
|
|
||||||
for _, r := range ms.reps {
|
|
||||||
if uint64(*r.Bandwidth) <= bitrate && (choice == nil || *r.Bandwidth > *choice.Bandwidth) {
|
|
||||||
choice = r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if choice != nil {
|
|
||||||
return choice
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can't support any of the bitrates, so find the lowest one.
|
|
||||||
for _, r := range ms.reps {
|
|
||||||
if choice == nil || *r.Bandwidth < *choice.Bandwidth {
|
|
||||||
choice = r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return choice
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the next segment in the stream
|
|
||||||
func (ms *MediaStream) Next(ctx context.Context) (segment *MediaSegment, err error) {
|
|
||||||
rep := ms.chooseRepresentation()
|
|
||||||
|
|
||||||
if rep.SegmentTemplate == nil {
|
|
||||||
return nil, fmt.Errorf("missing segment template")
|
|
||||||
}
|
|
||||||
|
|
||||||
if rep.SegmentTemplate.Media == nil {
|
|
||||||
return nil, fmt.Errorf("no media template")
|
|
||||||
}
|
|
||||||
|
|
||||||
if rep.SegmentTemplate.StartNumber == nil {
|
|
||||||
return nil, fmt.Errorf("missing start number")
|
|
||||||
}
|
|
||||||
|
|
||||||
path := *rep.SegmentTemplate.Media
|
|
||||||
sequence := ms.sequence + int(*rep.SegmentTemplate.StartNumber)
|
|
||||||
|
|
||||||
// TODO Support the full template engine
|
|
||||||
path = strings.ReplaceAll(path, "$RepresentationID$", *rep.ID)
|
|
||||||
path = strings.ReplaceAll(path, "$Number%05d$", fmt.Sprintf("%05d", sequence)) // TODO TODO
|
|
||||||
|
|
||||||
// Try openning the file
|
|
||||||
f, err := ms.Media.base.Open(path)
|
|
||||||
if errors.Is(err, os.ErrNotExist) && ms.sequence != 0 {
|
|
||||||
// Return EOF if the next file is missing
|
|
||||||
return nil, nil
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to open segment file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
duration := time.Duration(*rep.SegmentTemplate.Duration) / time.Nanosecond
|
|
||||||
timestamp := time.Duration(ms.sequence) * duration
|
|
||||||
|
|
||||||
init := ms.Media.inits[*rep.ID]
|
|
||||||
|
|
||||||
segment, err = newMediaSegment(ms, init, f, timestamp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create segment: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ms.sequence += 1
|
|
||||||
|
|
||||||
return segment, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MediaInit struct {
|
|
||||||
ID string
|
|
||||||
Raw []byte
|
|
||||||
Timescale int
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMediaInit(id string, raw []byte) (mi *MediaInit, err error) {
|
|
||||||
mi = new(MediaInit)
|
|
||||||
mi.ID = id
|
|
||||||
mi.Raw = raw
|
|
||||||
|
|
||||||
err = mi.parse()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse init segment: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return mi, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse through the init segment, literally just to populate the timescale
|
|
||||||
func (mi *MediaInit) parse() (err error) {
|
|
||||||
r := bytes.NewReader(mi.Raw)
|
|
||||||
|
|
||||||
_, err = mp4.ReadBoxStructure(r, func(h *mp4.ReadHandle) (interface{}, error) {
|
|
||||||
if !h.BoxInfo.IsSupportedType() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, _, err := h.ReadPayload()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch box := payload.(type) {
|
|
||||||
case *mp4.Mdhd: // Media Header; moov -> trak -> mdia > mdhd
|
|
||||||
if mi.Timescale != 0 {
|
|
||||||
// verify only one track
|
|
||||||
return nil, fmt.Errorf("multiple mdhd atoms")
|
|
||||||
}
|
|
||||||
|
|
||||||
mi.Timescale = int(box.Timescale)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expands children
|
|
||||||
return h.Expand()
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to parse MP4 file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MediaSegment struct {
|
|
||||||
Stream *MediaStream
|
|
||||||
Init *MediaInit
|
|
||||||
|
|
||||||
file fs.File
|
|
||||||
timestamp time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMediaSegment(s *MediaStream, init *MediaInit, file fs.File, timestamp time.Duration) (ms *MediaSegment, err error) {
|
|
||||||
ms = new(MediaSegment)
|
|
||||||
ms.Stream = s
|
|
||||||
ms.Init = init
|
|
||||||
|
|
||||||
ms.file = file
|
|
||||||
ms.timestamp = timestamp
|
|
||||||
|
|
||||||
return ms, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the next atom, sleeping based on the PTS to simulate a live stream
|
|
||||||
func (ms *MediaSegment) Read(ctx context.Context) (chunk []byte, err error) {
|
|
||||||
// Read the next top-level box
|
|
||||||
var header [8]byte
|
|
||||||
|
|
||||||
_, err = io.ReadFull(ms.file, header[:])
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read header: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
size := int(binary.BigEndian.Uint32(header[0:4]))
|
|
||||||
if size < 8 {
|
|
||||||
return nil, fmt.Errorf("box is too small")
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, size)
|
|
||||||
n := copy(buf, header[:])
|
|
||||||
|
|
||||||
_, err = io.ReadFull(ms.file, buf[n:])
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read atom: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sample, err := ms.parseAtom(ctx, buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse atom: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if sample != nil {
|
|
||||||
// Simulate a live stream by sleeping before we write this sample.
|
|
||||||
// Figure out how much time has elapsed since the start
|
|
||||||
elapsed := time.Since(ms.Stream.start)
|
|
||||||
delay := sample.Timestamp - elapsed
|
|
||||||
|
|
||||||
if delay > 0 {
|
|
||||||
// Sleep until we're supposed to see these samples
|
|
||||||
err = invoker.Sleep(delay)(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse through the MP4 atom, returning infomation about the next fragmented sample
|
|
||||||
func (ms *MediaSegment) parseAtom(ctx context.Context, buf []byte) (sample *mediaSample, err error) {
|
|
||||||
r := bytes.NewReader(buf)
|
|
||||||
|
|
||||||
_, err = mp4.ReadBoxStructure(r, func(h *mp4.ReadHandle) (interface{}, error) {
|
|
||||||
if !h.BoxInfo.IsSupportedType() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, _, err := h.ReadPayload()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch box := payload.(type) {
|
|
||||||
case *mp4.Moof:
|
|
||||||
sample = new(mediaSample)
|
|
||||||
case *mp4.Tfdt: // Track Fragment Decode Timestamp; moof -> traf -> tfdt
|
|
||||||
// TODO This box isn't required
|
|
||||||
// TODO we want the last PTS if there are multiple samples
|
|
||||||
var dts time.Duration
|
|
||||||
if box.FullBox.Version == 0 {
|
|
||||||
dts = time.Duration(box.BaseMediaDecodeTimeV0)
|
|
||||||
} else {
|
|
||||||
dts = time.Duration(box.BaseMediaDecodeTimeV1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ms.Init.Timescale == 0 {
|
|
||||||
return nil, fmt.Errorf("missing timescale")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to seconds
|
|
||||||
// TODO What about PTS?
|
|
||||||
sample.Timestamp = dts * time.Second / time.Duration(ms.Init.Timescale)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expands children
|
|
||||||
return h.Expand()
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse MP4 file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sample, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms *MediaSegment) Close() (err error) {
|
|
||||||
return ms.file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
type mediaSample struct {
|
|
||||||
Timestamp time.Duration // The timestamp of the first sample
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package warp
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
Init *MessageInit `json:"init,omitempty"`
|
|
||||||
Segment *MessageSegment `json:"segment,omitempty"`
|
|
||||||
Debug *MessageDebug `json:"debug,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MessageInit struct {
|
|
||||||
Id string `json:"id"` // ID of the init segment
|
|
||||||
}
|
|
||||||
|
|
||||||
type MessageSegment struct {
|
|
||||||
Init string `json:"init"` // ID of the init segment to use for this segment
|
|
||||||
Timestamp int `json:"timestamp"` // PTS of the first frame in milliseconds
|
|
||||||
}
|
|
||||||
|
|
||||||
type MessageDebug struct {
|
|
||||||
MaxBitrate int `json:"max_bitrate"` // Artificially limit the QUIC max bitrate
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
package warp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/kixelated/invoker"
|
|
||||||
"github.com/kixelated/quic-go"
|
|
||||||
"github.com/kixelated/quic-go/http3"
|
|
||||||
"github.com/kixelated/quic-go/logging"
|
|
||||||
"github.com/kixelated/quic-go/qlog"
|
|
||||||
"github.com/kixelated/webtransport-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
inner *webtransport.Server
|
|
||||||
media *Media
|
|
||||||
sessions invoker.Tasks
|
|
||||||
cert *tls.Certificate
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Addr string
|
|
||||||
Cert *tls.Certificate
|
|
||||||
LogDir string
|
|
||||||
Media *Media
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(config Config) (s *Server, err error) {
|
|
||||||
s = new(Server)
|
|
||||||
s.cert = config.Cert
|
|
||||||
s.media = config.Media
|
|
||||||
|
|
||||||
quicConfig := &quic.Config{}
|
|
||||||
|
|
||||||
if config.LogDir != "" {
|
|
||||||
quicConfig.Tracer = qlog.NewTracer(func(p logging.Perspective, connectionID []byte) io.WriteCloser {
|
|
||||||
path := fmt.Sprintf("%s-%s.qlog", p, hex.EncodeToString(connectionID))
|
|
||||||
|
|
||||||
f, err := os.Create(filepath.Join(config.LogDir, path))
|
|
||||||
if err != nil {
|
|
||||||
// lame
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return f
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{*s.cert},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Host a HTTP/3 server to serve the WebTransport endpoint
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/watch", s.handleWatch)
|
|
||||||
|
|
||||||
s.inner = &webtransport.Server{
|
|
||||||
H3: http3.Server{
|
|
||||||
TLSConfig: tlsConfig,
|
|
||||||
QuicConfig: quicConfig,
|
|
||||||
Addr: config.Addr,
|
|
||||||
Handler: mux,
|
|
||||||
},
|
|
||||||
CheckOrigin: func(r *http.Request) bool { return true },
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) runServe(ctx context.Context) (err error) {
|
|
||||||
return s.inner.ListenAndServe()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) runShutdown(ctx context.Context) (err error) {
|
|
||||||
<-ctx.Done()
|
|
||||||
s.inner.Close() // close on context shutdown
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Run(ctx context.Context) (err error) {
|
|
||||||
return invoker.Run(ctx, s.runServe, s.runShutdown, s.sessions.Repeat)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleWatch(w http.ResponseWriter, r *http.Request) {
|
|
||||||
hijacker, ok := w.(http3.Hijacker)
|
|
||||||
if !ok {
|
|
||||||
panic("unable to hijack connection: must use kixelated/quic-go")
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := hijacker.Connection()
|
|
||||||
|
|
||||||
sess, err := s.inner.Upgrade(w, r)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "failed to upgrade session", 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.serveSession(r.Context(), conn, sess)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) serveSession(ctx context.Context, conn quic.Connection, sess *webtransport.Session) (err error) {
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
sess.CloseWithError(1, err.Error())
|
|
||||||
} else {
|
|
||||||
sess.CloseWithError(0, "end of broadcast")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
ss, err := NewSession(conn, sess, s.media)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create session: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ss.Run(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("terminated session: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,279 +0,0 @@
|
||||||
package warp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/kixelated/invoker"
|
|
||||||
"github.com/kixelated/quic-go"
|
|
||||||
"github.com/kixelated/webtransport-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A single WebTransport session
|
|
||||||
type Session struct {
|
|
||||||
conn quic.Connection
|
|
||||||
inner *webtransport.Session
|
|
||||||
|
|
||||||
media *Media
|
|
||||||
inits map[string]*MediaInit
|
|
||||||
audio *MediaStream
|
|
||||||
video *MediaStream
|
|
||||||
|
|
||||||
streams invoker.Tasks
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSession(connection quic.Connection, session *webtransport.Session, media *Media) (s *Session, err error) {
|
|
||||||
s = new(Session)
|
|
||||||
s.conn = connection
|
|
||||||
s.inner = session
|
|
||||||
s.media = media
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) Run(ctx context.Context) (err error) {
|
|
||||||
s.inits, s.audio, s.video, err = s.media.Start(s.conn.GetMaxBandwidth)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to start media: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once we've validated the session, now we can start accessing the streams
|
|
||||||
return invoker.Run(ctx, s.runAccept, s.runAcceptUni, s.runInit, s.runAudio, s.runVideo, s.streams.Repeat)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) runAccept(ctx context.Context) (err error) {
|
|
||||||
for {
|
|
||||||
stream, err := s.inner.AcceptStream(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to accept bidirectional stream: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warp doesn't utilize bidirectional streams so just close them immediately.
|
|
||||||
// We might use them in the future so don't close the connection with an error.
|
|
||||||
stream.CancelRead(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) runAcceptUni(ctx context.Context) (err error) {
|
|
||||||
for {
|
|
||||||
stream, err := s.inner.AcceptUniStream(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to accept unidirectional stream: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.streams.Add(func(ctx context.Context) (err error) {
|
|
||||||
return s.handleStream(ctx, stream)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) handleStream(ctx context.Context, stream webtransport.ReceiveStream) (err error) {
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
stream.CancelRead(1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var header [8]byte
|
|
||||||
for {
|
|
||||||
_, err = io.ReadFull(stream, header[:])
|
|
||||||
if errors.Is(io.EOF, err) {
|
|
||||||
return nil
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("failed to read atom header: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
size := binary.BigEndian.Uint32(header[0:4])
|
|
||||||
name := string(header[4:8])
|
|
||||||
|
|
||||||
if size < 8 {
|
|
||||||
return fmt.Errorf("atom size is too small")
|
|
||||||
} else if size > 42069 { // arbitrary limit
|
|
||||||
return fmt.Errorf("atom size is too large")
|
|
||||||
} else if name != "warp" {
|
|
||||||
return fmt.Errorf("only warp atoms are supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := make([]byte, size-8)
|
|
||||||
|
|
||||||
_, err = io.ReadFull(stream, payload)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read atom payload: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("received message:", string(payload))
|
|
||||||
|
|
||||||
msg := Message{}
|
|
||||||
|
|
||||||
err = json.Unmarshal(payload, &msg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to decode json payload: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.Debug != nil {
|
|
||||||
s.setDebug(msg.Debug)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) runInit(ctx context.Context) (err error) {
|
|
||||||
for _, init := range s.inits {
|
|
||||||
err = s.writeInit(ctx, init)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write init stream: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) runAudio(ctx context.Context) (err error) {
|
|
||||||
for {
|
|
||||||
segment, err := s.audio.Next(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get next segment: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if segment == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.writeSegment(ctx, segment)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write segment stream: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) runVideo(ctx context.Context) (err error) {
|
|
||||||
for {
|
|
||||||
segment, err := s.video.Next(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get next segment: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if segment == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.writeSegment(ctx, segment)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write segment stream: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a stream for an INIT segment and write the container.
|
|
||||||
func (s *Session) writeInit(ctx context.Context, init *MediaInit) (err error) {
|
|
||||||
temp, err := s.inner.OpenUniStreamSync(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create stream: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if temp == nil {
|
|
||||||
// Not sure when this happens, perhaps when closing a connection?
|
|
||||||
return fmt.Errorf("received a nil stream from quic-go")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap the stream in an object that buffers writes instead of blocking.
|
|
||||||
stream := NewStream(temp)
|
|
||||||
s.streams.Add(stream.Run)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
stream.WriteCancel(1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
stream.SetPriority(math.MaxInt)
|
|
||||||
|
|
||||||
err = stream.WriteMessage(Message{
|
|
||||||
Init: &MessageInit{Id: init.ID},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write init header: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = stream.Write(init.Raw)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write init data: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = stream.Close()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to close init stream: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a stream for a segment and write the contents, chunk by chunk.
|
|
||||||
func (s *Session) writeSegment(ctx context.Context, segment *MediaSegment) (err error) {
|
|
||||||
temp, err := s.inner.OpenUniStreamSync(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create stream: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if temp == nil {
|
|
||||||
// Not sure when this happens, perhaps when closing a connection?
|
|
||||||
return fmt.Errorf("received a nil stream from quic-go")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap the stream in an object that buffers writes instead of blocking.
|
|
||||||
stream := NewStream(temp)
|
|
||||||
s.streams.Add(stream.Run)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
stream.WriteCancel(1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
ms := int(segment.timestamp / time.Millisecond)
|
|
||||||
|
|
||||||
// newer segments take priority
|
|
||||||
stream.SetPriority(ms)
|
|
||||||
|
|
||||||
err = stream.WriteMessage(Message{
|
|
||||||
Segment: &MessageSegment{
|
|
||||||
Init: segment.Init.ID,
|
|
||||||
Timestamp: ms,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write segment header: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
// Get the next fragment
|
|
||||||
buf, err := segment.Read(ctx)
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
break
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("failed to read segment data: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: This won't block because of our wrapper
|
|
||||||
_, err = stream.Write(buf)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write segment data: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = stream.Close()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to close segemnt stream: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) setDebug(msg *MessageDebug) {
|
|
||||||
s.conn.SetMaxBandwidth(uint64(msg.MaxBitrate))
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
package warp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/kixelated/webtransport-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Wrapper around quic.SendStream to make Write non-blocking.
|
|
||||||
// Otherwise we can't write to multiple concurrent streams in the same goroutine.
|
|
||||||
type Stream struct {
|
|
||||||
inner webtransport.SendStream
|
|
||||||
|
|
||||||
chunks [][]byte
|
|
||||||
closed bool
|
|
||||||
err error
|
|
||||||
|
|
||||||
notify chan struct{}
|
|
||||||
mutex sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStream(inner webtransport.SendStream) (s *Stream) {
|
|
||||||
s = new(Stream)
|
|
||||||
s.inner = inner
|
|
||||||
s.notify = make(chan struct{})
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) Run(ctx context.Context) (err error) {
|
|
||||||
defer func() {
|
|
||||||
s.mutex.Lock()
|
|
||||||
s.err = err
|
|
||||||
s.mutex.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
s.mutex.Lock()
|
|
||||||
|
|
||||||
chunks := s.chunks
|
|
||||||
notify := s.notify
|
|
||||||
closed := s.closed
|
|
||||||
|
|
||||||
s.chunks = s.chunks[len(s.chunks):]
|
|
||||||
s.mutex.Unlock()
|
|
||||||
|
|
||||||
for _, chunk := range chunks {
|
|
||||||
_, err = s.inner.Write(chunk)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if closed {
|
|
||||||
return s.inner.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(chunks) == 0 {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case <-notify:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) Write(buf []byte) (n int, err error) {
|
|
||||||
s.mutex.Lock()
|
|
||||||
defer s.mutex.Unlock()
|
|
||||||
|
|
||||||
if s.err != nil {
|
|
||||||
return 0, s.err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.closed {
|
|
||||||
return 0, fmt.Errorf("closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a copy of the buffer so it's long lived
|
|
||||||
buf = append([]byte{}, buf...)
|
|
||||||
s.chunks = append(s.chunks, buf)
|
|
||||||
|
|
||||||
// Wake up the writer
|
|
||||||
close(s.notify)
|
|
||||||
s.notify = make(chan struct{})
|
|
||||||
|
|
||||||
return len(buf), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) WriteMessage(msg Message) (err error) {
|
|
||||||
payload, err := json.Marshal(msg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to marshal message: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var size [4]byte
|
|
||||||
binary.BigEndian.PutUint32(size[:], uint32(len(payload)+8))
|
|
||||||
|
|
||||||
_, err = s.Write(size[:])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write size: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.Write([]byte("warp"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write atom header: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.Write(payload)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write payload: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) WriteCancel(code webtransport.StreamErrorCode) {
|
|
||||||
s.inner.CancelWrite(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) SetPriority(prio int) {
|
|
||||||
s.inner.SetPriority(prio)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) Close() (err error) {
|
|
||||||
s.mutex.Lock()
|
|
||||||
defer s.mutex.Unlock()
|
|
||||||
|
|
||||||
if s.err != nil {
|
|
||||||
return s.err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.closed = true
|
|
||||||
|
|
||||||
// Wake up the writer
|
|
||||||
close(s.notify)
|
|
||||||
s.notify = make(chan struct{})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
package web
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/kixelated/invoker"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
inner http.Server
|
|
||||||
config Config
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Addr string
|
|
||||||
CertFile string
|
|
||||||
KeyFile string
|
|
||||||
Fingerprint string // the TLS certificate fingerprint
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(config Config) (s *Server) {
|
|
||||||
s = new(Server)
|
|
||||||
s.config = config
|
|
||||||
|
|
||||||
s.inner = http.Server{
|
|
||||||
Addr: config.Addr,
|
|
||||||
}
|
|
||||||
|
|
||||||
http.HandleFunc("/fingerprint", s.handleFingerprint)
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Run(ctx context.Context) (err error) {
|
|
||||||
return invoker.Run(ctx, s.runServe, s.runShutdown)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) runServe(context.Context) (err error) {
|
|
||||||
// NOTE: Doesn't support context, which is why we need runShutdown
|
|
||||||
err = s.inner.ListenAndServeTLS(s.config.CertFile, s.config.KeyFile)
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gracefully shut down the server when the context is cancelled
|
|
||||||
func (s *Server) runShutdown(ctx context.Context) (err error) {
|
|
||||||
<-ctx.Done()
|
|
||||||
|
|
||||||
timeout, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
_ = s.inner.Shutdown(timeout)
|
|
||||||
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the sha256 of the certificate as a temporary work-around for local development.
|
|
||||||
// TODO remove this when WebTransport uses the system CA
|
|
||||||
func (s *Server) handleFingerprint(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
_, _ = w.Write([]byte(s.config.Fingerprint))
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/hex"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/kixelated/invoker"
|
|
||||||
"github.com/kixelated/warp/server/internal/warp"
|
|
||||||
"github.com/kixelated/warp/server/internal/web"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := run(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(ctx context.Context) (err error) {
|
|
||||||
addr := flag.String("addr", ":4443", "HTTPS server address")
|
|
||||||
cert := flag.String("tls-cert", "../cert/localhost.crt", "TLS certificate file path")
|
|
||||||
key := flag.String("tls-key", "../cert/localhost.key", "TLS certificate file path")
|
|
||||||
logDir := flag.String("log-dir", "", "logs will be written to the provided directory")
|
|
||||||
|
|
||||||
dash := flag.String("dash", "../media/playlist.mpd", "DASH playlist path")
|
|
||||||
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
media, err := warp.NewMedia(*dash)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open media: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsCert, err := tls.LoadX509KeyPair(*cert, *key)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to load TLS certificate: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
warpConfig := warp.Config{
|
|
||||||
Addr: *addr,
|
|
||||||
Cert: &tlsCert,
|
|
||||||
LogDir: *logDir,
|
|
||||||
Media: media,
|
|
||||||
}
|
|
||||||
|
|
||||||
warpServer, err := warp.New(warpConfig)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create warp server: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hash := sha256.Sum256(tlsCert.Certificate[0])
|
|
||||||
fingerprint := hex.EncodeToString(hash[:])
|
|
||||||
|
|
||||||
webConfig := web.Config{
|
|
||||||
Addr: *addr,
|
|
||||||
CertFile: *cert,
|
|
||||||
KeyFile: *key,
|
|
||||||
Fingerprint: fingerprint,
|
|
||||||
}
|
|
||||||
|
|
||||||
webServer := web.New(webConfig)
|
|
||||||
|
|
||||||
log.Printf("listening on %s", *addr)
|
|
||||||
|
|
||||||
return invoker.Run(ctx, invoker.Interrupt, warpServer.Run, webServer.Run)
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
Loading…
Reference in New Issue