Compare commits
754 Commits
v0.1.0
...
@livekit/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e099d367dd | ||
|
|
fb8bb59f73 | ||
|
|
aa335df02e | ||
|
|
e17caf57c8 | ||
|
|
82786f41fd | ||
|
|
b2dc8deac6 | ||
|
|
6dec8d27f9 | ||
|
|
288772c53c | ||
|
|
79b5ea9ea9 | ||
|
|
7814d0fbc2 | ||
|
|
f50408c48c | ||
|
|
e0c49d3c9d | ||
|
|
32c577d805 | ||
|
|
61e656c292 | ||
|
|
425cb974f7 | ||
|
|
725bc923f9 | ||
|
|
0411da6790 | ||
|
|
114aac165b | ||
|
|
1dd95af542 | ||
|
|
fcde125058 | ||
|
|
429fec77a6 | ||
|
|
d5e9f0a2f9 | ||
|
|
90207b41e2 | ||
|
|
c2a0176030 | ||
|
|
ec704e0cf2 | ||
|
|
142d7f108f | ||
|
|
c721a3ed39 | ||
|
|
2ec622e468 | ||
|
|
6410d008bf | ||
|
|
90dfd668f4 | ||
|
|
04d15340d0 | ||
|
|
47e4c6bba7 | ||
|
|
5aa3d97713 | ||
|
|
511f517b1a | ||
|
|
04372ebead | ||
|
|
30cee625bf | ||
|
|
93a26f478d | ||
|
|
dd69a5f011 | ||
|
|
e079b2245f | ||
|
|
38423424ed | ||
|
|
72c0789f38 | ||
|
|
5f0256e10a | ||
|
|
f547128159 | ||
|
|
e0c63d7698 | ||
|
|
c286435a2d | ||
|
|
151d757a97 | ||
|
|
e7962f4444 | ||
|
|
67ad5a7734 | ||
|
|
17f16c6d15 | ||
|
|
fd19ad24b8 | ||
|
|
2d50a79222 | ||
|
|
6d5a8047e6 | ||
|
|
bad3fc9684 | ||
|
|
524ca6a3a3 | ||
|
|
e418881086 | ||
|
|
da776daae7 | ||
|
|
91a1cc854d | ||
|
|
0bc8068edc | ||
|
|
2bce16cdcc | ||
|
|
b7e396271d | ||
|
|
cb008da1d6 | ||
|
|
2d633a51d8 | ||
|
|
e746785292 | ||
|
|
ef65d2b4c8 | ||
|
|
b6a979d8cf | ||
|
|
c69c1b0580 | ||
|
|
fec2972501 | ||
|
|
14e8de50d5 | ||
|
|
0519f4b851 | ||
|
|
bc6c7ffd71 | ||
|
|
fc68e8d82b | ||
|
|
766ababa37 | ||
|
|
93ff4a23a3 | ||
|
|
38f2e3216c | ||
|
|
e2c0cf8043 | ||
|
|
a512748415 | ||
|
|
a2f6423fd1 | ||
|
|
9b0ad21a6f | ||
|
|
0d9caadf76 | ||
|
|
62e1809e72 | ||
|
|
981fefbbe9 | ||
|
|
44e9dc34b4 | ||
|
|
4d22b4ce36 | ||
|
|
feb177f216 | ||
|
|
a9939bcd31 | ||
|
|
8d755b44a9 | ||
|
|
912f0cf1cd | ||
|
|
d676388462 | ||
|
|
b4eb83d841 | ||
|
|
de46ab020c | ||
|
|
cf54792d06 | ||
|
|
34e705a573 | ||
|
|
76870e1f15 | ||
|
|
00c977ffbb | ||
|
|
e96e23e254 | ||
|
|
61bb42ead5 | ||
|
|
47adc43f01 | ||
|
|
5726c611cb | ||
|
|
da55a5e169 | ||
|
|
497e77b264 | ||
|
|
fae28895ef | ||
|
|
0ea9ef145f | ||
|
|
bf6b141cd7 | ||
|
|
66b79e3526 | ||
|
|
2aa840f1f9 | ||
|
|
c04669aee6 | ||
|
|
37d8b0515d | ||
|
|
1552274d8d | ||
|
|
dde5199e52 | ||
|
|
50165a956a | ||
|
|
6dd778938f | ||
|
|
174a2f4bdb | ||
|
|
7a70c5da9b | ||
|
|
4f0d066ef6 | ||
|
|
92c5e52943 | ||
|
|
6e689b5c35 | ||
|
|
3339e807ff | ||
|
|
4a97b58328 | ||
|
|
19dc977fde | ||
|
|
454dff3b81 | ||
|
|
8a2fd58ff0 | ||
|
|
50cd638fea | ||
|
|
7efe51fa26 | ||
|
|
e8f01e457b | ||
|
|
0293e1ec08 | ||
|
|
a9e0faf6f0 | ||
|
|
233997882d | ||
|
|
6fbf4a2604 | ||
|
|
79e078b74c | ||
|
|
51aba73c05 | ||
|
|
2e75b8fb46 | ||
|
|
9d20a1af52 | ||
|
|
cee767e2d9 | ||
|
|
16bebde903 | ||
|
|
611223e34f | ||
|
|
ee031bdb59 | ||
|
|
d89487fdbc | ||
|
|
d010895a56 | ||
|
|
9c3dabb016 | ||
|
|
dd867b5144 | ||
|
|
eb3e3303ab | ||
|
|
cabaa7e10a | ||
|
|
e1abe07c1c | ||
|
|
39eb9f38a0 | ||
|
|
7394a1c07b | ||
|
|
c8f2018245 | ||
|
|
67252493b1 | ||
|
|
54f76fc686 | ||
|
|
31b03e6905 | ||
|
|
f56f11e568 | ||
|
|
3bd4ca5433 | ||
|
|
34c5703691 | ||
|
|
3b4fd6ea4a | ||
|
|
9e82495c04 | ||
|
|
9430659b3b | ||
|
|
4f497ff252 | ||
|
|
9786b97e9c | ||
|
|
cf33ad3861 | ||
|
|
6dc023d7ed | ||
|
|
e17b08e9a4 | ||
|
|
2e48332992 | ||
|
|
8ad4c79e2d | ||
|
|
3d9c1f2c79 | ||
|
|
701a31e4f6 | ||
|
|
0811dbfe03 | ||
|
|
a0e8241b1a | ||
|
|
4c708cfc9c | ||
|
|
4f3e8bebf9 | ||
|
|
ce67e4e290 | ||
|
|
01b5f081d6 | ||
|
|
e3a3fe05e3 | ||
|
|
bbf0ee4d3d | ||
|
|
00aac29859 | ||
|
|
9f667267f8 | ||
|
|
80e8b5a2d1 | ||
|
|
00894c56f4 | ||
|
|
927be345ba | ||
|
|
1442b24a4e | ||
|
|
e9e5fcd7d3 | ||
|
|
88366c462f | ||
|
|
b038d892cb | ||
|
|
b41cfc907b | ||
|
|
c09e8e07fd | ||
|
|
66b6b22962 | ||
|
|
8fb7e76324 | ||
|
|
3045ec7650 | ||
|
|
b5b083960d | ||
|
|
8950f73cd1 | ||
|
|
a2b800d10c | ||
|
|
297e4990e7 | ||
|
|
2ed21c5cf7 | ||
|
|
7bba17ea78 | ||
|
|
7a29c2ea3a | ||
|
|
4bd1ed6a02 | ||
|
|
a64882f692 | ||
|
|
5ffa9f0c65 | ||
|
|
afa0350bcd | ||
|
|
ec88d89da1 | ||
|
|
3b3c5b2329 | ||
|
|
6b5d53eb96 | ||
|
|
1c3f5fe919 | ||
|
|
ff80e6fd1b | ||
|
|
61e646265f | ||
|
|
fd7066503f | ||
|
|
1dda6d8eb0 | ||
|
|
3f612979d8 | ||
|
|
0cb26043c3 | ||
|
|
7a00fc1a03 | ||
|
|
aa43aa7482 | ||
|
|
9b360fc73e | ||
|
|
cfc741037c | ||
|
|
7afb922269 | ||
|
|
e65cc3e9fe | ||
|
|
6c267cfdfc | ||
|
|
43d65340ff | ||
|
|
625c7d8020 | ||
|
|
e230ee2d84 | ||
|
|
9020450e5f | ||
|
|
6d382559cf | ||
|
|
9f01c9ed76 | ||
|
|
4a12e0152e | ||
|
|
02a4a42603 | ||
|
|
873785a90b | ||
|
|
da745471e8 | ||
|
|
f8b1277b3c | ||
|
|
07ca9d4e47 | ||
|
|
b34cb4eb7f | ||
|
|
197fbc31df | ||
|
|
0a1ad709d1 | ||
|
|
5fd3ded802 | ||
|
|
26c91640f8 | ||
|
|
fac7f187fc | ||
|
|
25bcccdb94 | ||
|
|
102818c393 | ||
|
|
a8ab5b3eda | ||
|
|
37fc34151e | ||
|
|
97f4c927dd | ||
|
|
a187ed4f54 | ||
|
|
525419ade0 | ||
|
|
19fdb609a9 | ||
|
|
5b156a9beb | ||
|
|
a020215c5c | ||
|
|
a73f47fb8b | ||
|
|
281e00a4a6 | ||
|
|
fcfd77e3a3 | ||
|
|
b6137b803b | ||
|
|
9087b5d95a | ||
|
|
821c244d8c | ||
|
|
0708b5a5bb | ||
|
|
dd24d07183 | ||
|
|
eae220ae8d | ||
|
|
cf9f95141e | ||
|
|
933a0f5464 | ||
|
|
1f2c03c3e5 | ||
|
|
2770beb37a | ||
|
|
47d330eafb | ||
|
|
0b9fd43dce | ||
|
|
60ffcadbac | ||
|
|
8dbc929ca4 | ||
|
|
c32b942fd3 | ||
|
|
2d65160247 | ||
|
|
fc1aa19ec2 | ||
|
|
079ca0da61 | ||
|
|
946a39bf5d | ||
|
|
ce275be4e6 | ||
|
|
dfb5162c7c | ||
|
|
9f0a8f87da | ||
|
|
ecca17670d | ||
|
|
eb956a83ae | ||
|
|
923c1cfa18 | ||
|
|
74c0ca0a5b | ||
|
|
13abb13b71 | ||
|
|
e0eaae8584 | ||
|
|
14b7eb70d2 | ||
|
|
f2f3cad866 | ||
|
|
6b455c1116 | ||
|
|
3892d26946 | ||
|
|
24573198ef | ||
|
|
536a3e888e | ||
|
|
f31745a2f2 | ||
|
|
f3bba12b3e | ||
|
|
405389fa48 | ||
|
|
d791f31b6e | ||
|
|
caf65ebc47 | ||
|
|
881a872e15 | ||
|
|
3cd286c9c2 | ||
|
|
d45d830f69 | ||
|
|
5ca6b8f6bc | ||
|
|
f38c24c72d | ||
|
|
e1a077ae92 | ||
|
|
c9e9ae555f | ||
|
|
49975d3873 | ||
|
|
357ef3ca23 | ||
|
|
e3b3e994e9 | ||
|
|
5cbcc24208 | ||
|
|
cd2fe7d63f | ||
|
|
1cc5a5d915 | ||
|
|
9d0d6c9e94 | ||
|
|
3c53edc919 | ||
|
|
71d539707e | ||
|
|
7d8704997b | ||
|
|
b0d723aaa9 | ||
|
|
193f7bd191 | ||
|
|
fc3437606a | ||
|
|
fefcd74b77 | ||
|
|
8460641348 | ||
|
|
99e39d9896 | ||
|
|
424b21fd0e | ||
|
|
448dac4d68 | ||
|
|
e5323cdb4a | ||
|
|
8e67918879 | ||
|
|
cda2a8ac91 | ||
|
|
0644d9a979 | ||
|
|
5cff0336ab | ||
|
|
519c96683d | ||
|
|
c108af2ebf | ||
|
|
ce9e603cfd | ||
|
|
089430752e | ||
|
|
89359963fc | ||
|
|
7d128913f3 | ||
|
|
6cb3e577e2 | ||
|
|
0aad9fb9e7 | ||
|
|
1838b80b57 | ||
|
|
01ae0cb0fa | ||
|
|
7f36858dcf | ||
|
|
d8cc358109 | ||
|
|
a186ecb11a | ||
|
|
89c91393d0 | ||
|
|
2a4d1a607b | ||
|
|
262f7eca9b | ||
|
|
b8c7e33fa0 | ||
|
|
dbea81ff07 | ||
|
|
52cca4239c | ||
|
|
4c2100267b | ||
|
|
efad03ffe4 | ||
|
|
eefdb3b712 | ||
|
|
8999a6b785 | ||
|
|
3af63581bf | ||
|
|
08ebbda448 | ||
|
|
c3734b9b46 | ||
|
|
c5dc103838 | ||
|
|
26fe70550b | ||
|
|
40c26a892d | ||
|
|
cadccd5410 | ||
|
|
e2f4e65f58 | ||
|
|
ac74d1e920 | ||
|
|
6c978d46a6 | ||
|
|
466f2f5cfe | ||
|
|
22791f5141 | ||
|
|
dcea9e925a | ||
|
|
b2974692eb | ||
|
|
59f4b65928 | ||
|
|
db5afb1c7f | ||
|
|
79477e28a1 | ||
|
|
5ea01f3d4a | ||
|
|
c54f5f7f41 | ||
|
|
33317cadbc | ||
|
|
eee263b380 | ||
|
|
a68f38ce7d | ||
|
|
eb3ef0f4bf | ||
|
|
6707e562d4 | ||
|
|
3058e52593 | ||
|
|
31f880940e | ||
|
|
0975cb52f6 | ||
|
|
1e793a8870 | ||
|
|
d2054890a6 | ||
|
|
f54aaadb8c | ||
|
|
2f08c82d22 | ||
|
|
5fea69067b | ||
|
|
bfe64b24ff | ||
|
|
2956c3424f | ||
|
|
f70173e98e | ||
|
|
0947b07013 | ||
|
|
30f8b4c081 | ||
|
|
0f2f2cde6f | ||
|
|
b911c52c6d | ||
|
|
48e7d8e34e | ||
|
|
927c9ea2b4 | ||
|
|
802309c658 | ||
|
|
8c8b87686b | ||
|
|
47b8b5fce6 | ||
|
|
a1a39a16f4 | ||
|
|
86bb95f8e6 | ||
|
|
c2bf62e740 | ||
|
|
2336d2c655 | ||
|
|
f1bba9bc14 | ||
|
|
7488296390 | ||
|
|
7d4c517bf4 | ||
|
|
e78c5b18c0 | ||
|
|
ffdcdd67ac | ||
|
|
379e1d3084 | ||
|
|
6b747fe252 | ||
|
|
8fcafa0923 | ||
|
|
5cdd7c494f | ||
|
|
6d1cd857eb | ||
|
|
c3e5be2aff | ||
|
|
a1819deeab | ||
|
|
c2dcec981a | ||
|
|
a094b92547 | ||
|
|
176d6d15b6 | ||
|
|
c0705dbb69 | ||
|
|
dac2223d7a | ||
|
|
721dbba55d | ||
|
|
073251b64a | ||
|
|
5fa71af6c3 | ||
|
|
2cf53863d5 | ||
|
|
43f5fc64e9 | ||
|
|
e946da680c | ||
|
|
26f188cb84 | ||
|
|
cc52c6ef83 | ||
|
|
563f18426b | ||
|
|
8a6b0e0562 | ||
|
|
4a1ada9899 | ||
|
|
3db2ee16ef | ||
|
|
ec5189c9fd | ||
|
|
9a57a8c5b2 | ||
|
|
812bcb9904 | ||
|
|
800036bdb4 | ||
|
|
410ca1ce35 | ||
|
|
f6d32e15b0 | ||
|
|
a6f6dde5db | ||
|
|
5a66fc1d10 | ||
|
|
358df22e41 | ||
|
|
c71216774a | ||
|
|
fa1edc35c8 | ||
|
|
cacdab7b41 | ||
|
|
9987dca3a3 | ||
|
|
a1d02655b0 | ||
|
|
f55bf39335 | ||
|
|
10b378e3bc | ||
|
|
b75d0aed9e | ||
|
|
8368e5ada7 | ||
|
|
d208f34298 | ||
|
|
7c8172cb6e | ||
|
|
8a49ab3e6c | ||
|
|
abfad31c5f | ||
|
|
f637ca8e67 | ||
|
|
34cae0997a | ||
|
|
fa87d56355 | ||
|
|
d4406dd279 | ||
|
|
d9b5a759b9 | ||
|
|
fc943512b0 | ||
|
|
e586f56c17 | ||
|
|
e43a6056a6 | ||
|
|
5875a37d2c | ||
|
|
46717d5070 | ||
|
|
5420927b79 | ||
|
|
7e96a46219 | ||
|
|
524a374588 | ||
|
|
e20cc76010 | ||
|
|
ec328397c7 | ||
|
|
e2424e9a12 | ||
|
|
c04cca18e5 | ||
|
|
56b42b9030 | ||
|
|
b1eab0ff38 | ||
|
|
9303619f01 | ||
|
|
0f6e21a1e3 | ||
|
|
5353facf2d | ||
|
|
86e2d92816 | ||
|
|
8449c11069 | ||
|
|
92c1211d70 | ||
|
|
d3635b1226 | ||
|
|
e5d7144e26 | ||
|
|
5b5031cf27 | ||
|
|
b01c630e70 | ||
|
|
41f5b55c95 | ||
|
|
659e8f58fb | ||
|
|
8810b9cc84 | ||
|
|
b80cd331f2 | ||
|
|
3f47968225 | ||
|
|
f69b436be8 | ||
|
|
2a4be29988 | ||
|
|
9b09be77ef | ||
|
|
cd9e72d48a | ||
|
|
85bf30570f | ||
|
|
b0c33b869a | ||
|
|
21e14f319a | ||
|
|
19dd0a128e | ||
|
|
d9bc6cd9cb | ||
|
|
f5abcac3a5 | ||
|
|
698ea84725 | ||
|
|
a17234b098 | ||
|
|
af8105ee44 | ||
|
|
44a5ed6b4d | ||
|
|
dc2a7bc3a0 | ||
|
|
bafacf7041 | ||
|
|
19ead11853 | ||
|
|
e982494841 | ||
|
|
8645a138fb | ||
|
|
782f517a6e | ||
|
|
581466192d | ||
|
|
df6fc3665f | ||
|
|
b6794d3f26 | ||
|
|
36c8b27288 | ||
|
|
2b91fd8ace | ||
|
|
e8fddf3452 | ||
|
|
a193b475b9 | ||
|
|
6ec04e9ca4 | ||
|
|
ca55e87895 | ||
|
|
df2069bbbe | ||
|
|
284d6b2729 | ||
|
|
093984de8e | ||
|
|
0291fd7fdf | ||
|
|
28c92d8f69 | ||
|
|
d8431e35ee | ||
|
|
9cbacea721 | ||
|
|
cda57bb73f | ||
|
|
a1c2809ddc | ||
|
|
cd39f4ca4d | ||
|
|
ac9f2c2b91 | ||
|
|
613285ea6f | ||
|
|
bd098187d8 | ||
|
|
ac91aad4ad | ||
|
|
937fb9ce9f | ||
|
|
df83c83769 | ||
|
|
d76090cba2 | ||
|
|
e09066ac7e | ||
|
|
cfae06c397 | ||
|
|
2d0d2484b0 | ||
|
|
dc99a5ad3c | ||
|
|
ae34dc3ec4 | ||
|
|
67539ebcab | ||
|
|
a358bbbcc9 | ||
|
|
ac26418a52 | ||
|
|
ad9c55ddef | ||
|
|
cb6a3a0942 | ||
|
|
5eea37b737 | ||
|
|
86da225219 | ||
|
|
59d9284912 | ||
|
|
7250306954 | ||
|
|
47777c7162 | ||
|
|
c9236084b6 | ||
|
|
ed3bb7fac5 | ||
|
|
8e6746864e | ||
|
|
3911a509c1 | ||
|
|
fbd9c34bc0 | ||
|
|
cb05bd0f1b | ||
|
|
3834dd8302 | ||
|
|
8b26199464 | ||
|
|
1c257e2045 | ||
|
|
8f61585578 | ||
|
|
245526104d | ||
|
|
f9c9c8da7f | ||
|
|
dfc58f1dc1 | ||
|
|
9968158c02 | ||
|
|
d506ba1dc9 | ||
|
|
de8ecdefc9 | ||
|
|
54e2e20ad3 | ||
|
|
8bb739d1b0 | ||
|
|
8778627ef3 | ||
|
|
1afd55ebd0 | ||
|
|
1cadd9613c | ||
|
|
df4d3bb245 | ||
|
|
9d760dc384 | ||
|
|
cad53a3185 | ||
|
|
d81614de18 | ||
|
|
f03f3734be | ||
|
|
137b876761 | ||
|
|
31ce725fa5 | ||
|
|
5f996eb446 | ||
|
|
aaed1a401b | ||
|
|
f3de65a58b | ||
|
|
eb06e1619a | ||
|
|
e65f582336 | ||
|
|
4a8409f8f2 | ||
|
|
d1616e3cb8 | ||
|
|
e66f32ebb7 | ||
|
|
fa5221e452 | ||
|
|
5e07772153 | ||
|
|
b8779c1fd4 | ||
|
|
e36e6426a0 | ||
|
|
57d294a934 | ||
|
|
7e4ab2da14 | ||
|
|
51a8116f88 | ||
|
|
2ecd4805e7 | ||
|
|
ff07c68fb2 | ||
|
|
91c49df893 | ||
|
|
084233d23a | ||
|
|
377f74b29d | ||
|
|
688e4f627b | ||
|
|
cef16c8ef3 | ||
|
|
fa9efaff8c | ||
|
|
e3f2240801 | ||
|
|
25b60c4bb0 | ||
|
|
7a6bb27499 | ||
|
|
cacdaa5bf0 | ||
|
|
0d59783336 | ||
|
|
fd7cd24bac | ||
|
|
539c40c917 | ||
|
|
3c712ad5c9 | ||
|
|
f5e8e20530 | ||
|
|
54544c489c | ||
|
|
6ba1221db3 | ||
|
|
56fa20a020 | ||
|
|
8dff6f89d2 | ||
|
|
51f5188bc4 | ||
|
|
6f2a49e449 | ||
|
|
5cea5d444f | ||
|
|
a91198e400 | ||
|
|
874d12bf8b | ||
|
|
dbd455d2c1 | ||
|
|
f3ffabe240 | ||
|
|
4b44edff9e | ||
|
|
078505af8b | ||
|
|
fd3aaffcb8 | ||
|
|
13b2038518 | ||
|
|
82e5542f21 | ||
|
|
2d72f8bc52 | ||
|
|
d8c251e132 | ||
|
|
896d5ae1df | ||
|
|
5b8573f2a6 | ||
|
|
946db440fa | ||
|
|
ec6020d865 | ||
|
|
28685be063 | ||
|
|
fed47fd8d1 | ||
|
|
7f83990101 | ||
|
|
40df25091b | ||
|
|
e71d5db387 | ||
|
|
3d4fe0c532 | ||
|
|
d79c2afbfc | ||
|
|
77f0aed23c | ||
|
|
b94955852f | ||
|
|
01c702cc14 | ||
|
|
e3f90e2957 | ||
|
|
bde53c19d1 | ||
|
|
a4208afda1 | ||
|
|
71562c1a1a | ||
|
|
5a6a910c76 | ||
|
|
dd7165e713 | ||
|
|
d2b420f73d | ||
|
|
a7f937bb7b | ||
|
|
2c4c8d7764 | ||
|
|
c441eb5f03 | ||
|
|
feaea540b8 | ||
|
|
0b14193039 | ||
|
|
cfba8f9933 | ||
|
|
dfabd14143 | ||
|
|
88ab66e0e7 | ||
|
|
550362e3f0 | ||
|
|
b146ec0953 | ||
|
|
da651cb088 | ||
|
|
cf8fab999a | ||
|
|
0895daea96 | ||
|
|
d2f1ed9110 | ||
|
|
f77609470e | ||
|
|
ab99909273 | ||
|
|
81dc057627 | ||
|
|
8785fbf5c1 | ||
|
|
35d468c37d | ||
|
|
c7e8051ed6 | ||
|
|
dfd89d696f | ||
|
|
70f4073aa0 | ||
|
|
9b83b60b2e | ||
|
|
8de3224f92 | ||
|
|
b5eee90222 | ||
|
|
9ba7d5ce77 | ||
|
|
05b3888f5a | ||
|
|
9e08a795f4 | ||
|
|
3280169f2d | ||
|
|
6872ea1637 | ||
|
|
be219082bb | ||
|
|
a61c6c03f7 | ||
|
|
91c31dabb0 | ||
|
|
b3847c8d35 | ||
|
|
4dede7984e | ||
|
|
f73cb2ec8d | ||
|
|
1e9063c438 | ||
|
|
f3bd2c316e | ||
|
|
bde5600f68 | ||
|
|
bae8dd8bc5 | ||
|
|
125748935a | ||
|
|
25ee0223e5 | ||
|
|
bc7778f2a7 | ||
|
|
dcb35c40c9 | ||
|
|
4d580badfd | ||
|
|
5e7fe78240 | ||
|
|
52cda35f86 | ||
|
|
ffb5cb48d0 | ||
|
|
1dadf89309 | ||
|
|
c90319ba3b | ||
|
|
3b3b0e68ec | ||
|
|
540b5df27f | ||
|
|
a054fef89e | ||
|
|
0f9ae3e730 | ||
|
|
e3e7ce0232 | ||
|
|
b60237c535 | ||
|
|
4472a315d9 | ||
|
|
9afc4773a6 | ||
|
|
8ecd5eeca7 | ||
|
|
4a04d5c374 | ||
|
|
055674311d | ||
|
|
2b75ba9799 | ||
|
|
e3b107f710 | ||
|
|
e6c77cb1df | ||
|
|
4d124d0a9e | ||
|
|
be8bcf07f0 | ||
|
|
e1a7347506 | ||
|
|
1ecf8e9a45 | ||
|
|
0f1f07c6fe | ||
|
|
11c6a1d9ee | ||
|
|
054f93553c | ||
|
|
619cad0f3a | ||
|
|
5e3f9f4013 | ||
|
|
94556abf61 | ||
|
|
e400b89dd4 | ||
|
|
ce6c95a5d7 | ||
|
|
a7c76b98b8 | ||
|
|
5a1fc089af | ||
|
|
17ea3742d0 | ||
|
|
9eaf4db7ff | ||
|
|
d60d683ac8 | ||
|
|
46321822a0 | ||
|
|
6319eef0ca | ||
|
|
8ce287b9f7 | ||
|
|
04db819c8e | ||
|
|
8a63993232 | ||
|
|
8d0a9538d3 | ||
|
|
992c4b2da9 | ||
|
|
9caa97ddfc | ||
|
|
4c64ffcf86 | ||
|
|
0bcc2a37a8 | ||
|
|
43e9e95940 | ||
|
|
f50e369785 | ||
|
|
1f2364dac4 | ||
|
|
031bb23807 | ||
|
|
71fedf0a8a | ||
|
|
ccbb4aba6e | ||
|
|
602726cf90 | ||
|
|
eb172da5e6 | ||
|
|
54ceb519ad | ||
|
|
f025d10a3d | ||
|
|
36adae5957 | ||
|
|
b806a0c87d | ||
|
|
7402ef7903 | ||
|
|
fb9ed845bd | ||
|
|
367ddff398 | ||
|
|
f6980aac49 | ||
|
|
a93de94603 | ||
|
|
f7e29983dd | ||
|
|
416943d37e | ||
|
|
5edd2b18f8 | ||
|
|
3b0a05aaaa | ||
|
|
64e12ed39e | ||
|
|
8860ae3a5d | ||
|
|
a6c9b46d03 | ||
|
|
dd96c3f6b1 | ||
|
|
654838f6e8 | ||
|
|
6a204962ac | ||
|
|
88f6f707e6 | ||
|
|
27ff355a51 | ||
|
|
b7893e35ae | ||
|
|
0491a5048e | ||
|
|
5fb296a571 | ||
|
|
b4b2437b4e |
8
.changeset/README.md
Normal file
8
.changeset/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||
15
.changeset/config.json
Normal file
15
.changeset/config.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
|
||||
"changelog": [
|
||||
"@livekit/changesets-changelog-github",
|
||||
{ "repo": "livekit/protocol" }
|
||||
],
|
||||
"commit": false,
|
||||
"fixed": [["github.com/livekit/protocol", "@livekit/protocol"]],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": [],
|
||||
"privatePackages": { "version": true, "tag": true }
|
||||
}
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
packages/js/src/gen/* linguist-generated=true
|
||||
57
.github/workflows/buildtest.yaml
vendored
Normal file
57
.github/workflows/buildtest.yaml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
# Copyright 2023 LiveKit, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
name: Test
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
~/go/bin
|
||||
~/.cache
|
||||
key: livekit-protocol
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: Set up gotestfmt
|
||||
run: go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@v2.4.1
|
||||
|
||||
- name: Download Go modules
|
||||
run: go mod download
|
||||
|
||||
- name: Static Check
|
||||
uses: amarpal/staticcheck-action@master
|
||||
with:
|
||||
checks: '["all", "-ST1000", "-ST1003", "-ST1020", "-ST1021", "-ST1022", "-SA1019"]'
|
||||
install-go: false
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
set -euo pipefail
|
||||
MallocNanoZone=0 go test -race -json -v ./... 2>&1 | gotestfmt
|
||||
75
.github/workflows/generate.yaml
vendored
Normal file
75
.github/workflows/generate.yaml
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
# Copyright 2023 LiveKit, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
name: Generate
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
|
||||
jobs:
|
||||
generate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ">=1.20"
|
||||
|
||||
- name: Go mod tidy
|
||||
run: go mod tidy
|
||||
|
||||
- name: Install generators
|
||||
uses: magefile/mage-action@v3
|
||||
with:
|
||||
version: latest
|
||||
install-only: true
|
||||
|
||||
- name: Bootstrap
|
||||
run: mage bootstrap
|
||||
|
||||
- name: Generate Protobuf
|
||||
run: mage proto
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Test generate JS files
|
||||
run: |
|
||||
pnpm --filter @livekit/protocol run generate:proto
|
||||
|
||||
- name: Add changes
|
||||
uses: EndBug/add-and-commit@v9
|
||||
with:
|
||||
add: '["livekit", "rpc", "infra"]'
|
||||
default_author: github_actions
|
||||
message: generated protobuf
|
||||
52
.github/workflows/release.yaml
vendored
Normal file
52
.github/workflows/release.yaml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Create Release Pull Request or Publish packages
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
# This expects you to have a script called ci:publish which does a build for your packages and calls changeset publish
|
||||
publish: pnpm ci:publish
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Create git tag for golang package
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
uses: rickstaa/action-create-tag@v1
|
||||
id: tag_create
|
||||
with:
|
||||
tag: ${{format('v{0}', fromJson(steps.changesets.outputs.publishedPackages)[0].version)}}
|
||||
tag_exists_error: false
|
||||
message: "github.com/livekit/protocol@${{fromJson(steps.changesets.outputs.publishedPackages)[0].version}}"
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -13,3 +13,6 @@
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
.idea/
|
||||
|
||||
node_modules/
|
||||
|
||||
2
.idea/.gitignore
generated
vendored
2
.idea/.gitignore
generated
vendored
@@ -6,3 +6,5 @@
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# GitHub Copilot persisted chat sessions
|
||||
/copilot/chatSessions
|
||||
|
||||
16
.idea/modules.xml
generated
16
.idea/modules.xml
generated
@@ -1,4 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright 2023 LiveKit, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
|
||||
17
.idea/protoeditor.xml
generated
17
.idea/protoeditor.xml
generated
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProtobufLanguageSettings">
|
||||
<option name="autoConfigEnabled" value="false" />
|
||||
<option name="importPathEntries">
|
||||
<list>
|
||||
<ImportPathEntry>
|
||||
<option name="location" value="jar://$APPLICATION_PLUGINS_DIR$/protobuf-editor.jar!/include" />
|
||||
</ImportPathEntry>
|
||||
<ImportPathEntry>
|
||||
<option name="location" value="file://$PROJECT_DIR$" />
|
||||
</ImportPathEntry>
|
||||
</list>
|
||||
</option>
|
||||
<option name="descriptorPath" value="google/protobuf/descriptor.proto" />
|
||||
</component>
|
||||
</project>
|
||||
16
.idea/vcs.xml
generated
16
.idea/vcs.xml
generated
@@ -1,4 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright 2023 LiveKit, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
|
||||
8
.idea/watcherTasks.xml
generated
8
.idea/watcherTasks.xml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectTasksOptions">
|
||||
<enabled-global>
|
||||
<option value="goimports" />
|
||||
</enabled-global>
|
||||
</component>
|
||||
</project>
|
||||
119
CHANGELOG.md
Normal file
119
CHANGELOG.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# github.com/livekit/protocol
|
||||
|
||||
## 1.19.1
|
||||
|
||||
## 1.19.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add SIP grants. - [#745](https://github.com/livekit/protocol/pull/745) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fix typo in SIP Trunk conversion. - [#744](https://github.com/livekit/protocol/pull/744) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
## 1.18.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Split SIP Trunk configuration to inbound and outbound parts. - [#738](https://github.com/livekit/protocol/pull/738) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
- add Participant attributes key/val storage - [#733](https://github.com/livekit/protocol/pull/733) ([@davidzhao](https://github.com/davidzhao))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Simplify number matching rules for SIP. - [#737](https://github.com/livekit/protocol/pull/737) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
- Added session token option for s3 uploads - [#734](https://github.com/livekit/protocol/pull/734) ([@frostbyte73](https://github.com/frostbyte73))
|
||||
|
||||
- Include analytics event ids - [#739](https://github.com/livekit/protocol/pull/739) ([@davidzhao](https://github.com/davidzhao))
|
||||
|
||||
## 1.17.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add support for additional SIP transports. - [#719](https://github.com/livekit/protocol/pull/719) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
- Add ICERestartWHIPResource to RPC - [#716](https://github.com/livekit/protocol/pull/716) ([@Sean-Der](https://github.com/Sean-Der))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Added error code field to EgressInfo - [#714](https://github.com/livekit/protocol/pull/714) ([@frostbyte73](https://github.com/frostbyte73))
|
||||
|
||||
- Include node_id with analytics events - [#727](https://github.com/livekit/protocol/pull/727) ([@davidzhao](https://github.com/davidzhao))
|
||||
|
||||
- Match SIP Trunks by source IP or mask. - [#724](https://github.com/livekit/protocol/pull/724) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
- Add notice to use participant identity from DataPacket for transcriptions. - [#706](https://github.com/livekit/protocol/pull/706) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
## 1.16.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- - Add RedactAutoEncodedOutput to support redacting auto egress types - [#712](https://github.com/livekit/protocol/pull/712) ([@biglittlebigben](https://github.com/biglittlebigben))
|
||||
- Redact image outputs
|
||||
- Support AliOSS uploads for auto egress
|
||||
|
||||
- Allow inbound number filtering on SIP Dispatch Rules - [#707](https://github.com/livekit/protocol/pull/707) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
- Move egress request redacting routines to protocol - [#711](https://github.com/livekit/protocol/pull/711) ([@biglittlebigben](https://github.com/biglittlebigben))
|
||||
|
||||
## 1.15.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Expose Logger constructor for Zap. - [#701](https://github.com/livekit/protocol/pull/701) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
- Add metadata to SIP trunks, dispatch rules and participants. Change SIP participant identity prefix to `sip_`. - [#696](https://github.com/livekit/protocol/pull/696) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
- Move language into TranscriptionSegment #703 - [#704](https://github.com/livekit/protocol/pull/704) ([@lukasIO](https://github.com/lukasIO))
|
||||
|
||||
## 1.14.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Added real-time Transcription protocol - [#686](https://github.com/livekit/protocol/pull/686) ([@davidzhao](https://github.com/davidzhao))
|
||||
|
||||
- WHIP protocol change - [#680](https://github.com/livekit/protocol/pull/680) ([@biglittlebigben](https://github.com/biglittlebigben))
|
||||
|
||||
Deprecate the bypass_transcoding property in all ingress APIs and introduce the optional enable_transcoding property. This property will default to false for WHIP and to true for all other ingress types.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Add SIP participant name. - [#687](https://github.com/livekit/protocol/pull/687) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
## 1.13.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add and option to play ringtone for SIP outbound calls. - [#671](https://github.com/livekit/protocol/pull/671) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Add initial support for slog. - [#668](https://github.com/livekit/protocol/pull/668) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
## 1.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Moved CPU stats to a separate hwstats package, removing cgo dependency. - [#660](https://github.com/livekit/protocol/pull/660) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fixed bounds check when masking short SIP numbers. - [#650](https://github.com/livekit/protocol/pull/650) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
- Add signal requests for local track updates - [#651](https://github.com/livekit/protocol/pull/651) ([@lukasIO](https://github.com/lukasIO))
|
||||
|
||||
- Allow sending DTMF when creating SIP participant. - [#658](https://github.com/livekit/protocol/pull/658) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
## 1.11.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Align package version with manually tagged golang module - [#646](https://github.com/livekit/protocol/pull/646) ([@lukasIO](https://github.com/lukasIO))
|
||||
|
||||
## 1.10.4
|
||||
|
||||
## 1.10.3
|
||||
|
||||
## 1.10.2
|
||||
215
LICENSE
215
LICENSE
@@ -1,21 +1,202 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 livekit
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
1. Definitions.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
13
NOTICE
Normal file
13
NOTICE
Normal file
@@ -0,0 +1,13 @@
|
||||
Copyright 2023 LiveKit, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
31
agent/token.go
Normal file
31
agent/token.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
func BuildAgentToken(apiKey, secret, roomName, participantIdentity, participantName, participantMetadata string, permissions *livekit.ParticipantPermission) (string, error) {
|
||||
grant := &auth.VideoGrant{
|
||||
RoomJoin: true,
|
||||
Agent: true,
|
||||
Room: roomName,
|
||||
CanSubscribe: &permissions.CanSubscribe,
|
||||
CanPublish: &permissions.CanPublish,
|
||||
CanPublishData: &permissions.CanPublishData,
|
||||
Hidden: permissions.Hidden,
|
||||
CanUpdateOwnMetadata: &permissions.CanUpdateMetadata,
|
||||
}
|
||||
|
||||
at := auth.NewAccessToken(apiKey, secret).
|
||||
AddGrant(grant).
|
||||
SetIdentity(participantIdentity).
|
||||
SetName(participantName).
|
||||
SetKind(livekit.ParticipantInfo_AGENT).
|
||||
SetValidFor(1 * time.Hour).
|
||||
SetMetadata(participantMetadata)
|
||||
|
||||
return at.ToJWT()
|
||||
}
|
||||
@@ -1,24 +1,38 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
"github.com/go-jose/go-jose/v3"
|
||||
"github.com/go-jose/go-jose/v3/jwt"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultValidDuration = 6 * time.Hour
|
||||
)
|
||||
|
||||
// Signer that produces token signed with API key and secret
|
||||
// AccessToken produces token signed with API key and secret
|
||||
type AccessToken struct {
|
||||
apiKey string
|
||||
secret string
|
||||
identity string
|
||||
videoGrant *VideoGrant
|
||||
metadata map[string]interface{}
|
||||
validFor time.Duration
|
||||
apiKey string
|
||||
secret string
|
||||
grant ClaimGrants
|
||||
validFor time.Duration
|
||||
}
|
||||
|
||||
func NewAccessToken(key string, secret string) *AccessToken {
|
||||
@@ -29,7 +43,7 @@ func NewAccessToken(key string, secret string) *AccessToken {
|
||||
}
|
||||
|
||||
func (t *AccessToken) SetIdentity(identity string) *AccessToken {
|
||||
t.identity = identity
|
||||
t.grant.Identity = identity
|
||||
return t
|
||||
}
|
||||
|
||||
@@ -38,13 +52,46 @@ func (t *AccessToken) SetValidFor(duration time.Duration) *AccessToken {
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *AccessToken) AddGrant(grant *VideoGrant) *AccessToken {
|
||||
t.videoGrant = grant
|
||||
func (t *AccessToken) SetName(name string) *AccessToken {
|
||||
t.grant.Name = name
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *AccessToken) SetMetadata(md map[string]interface{}) *AccessToken {
|
||||
t.metadata = md
|
||||
func (t *AccessToken) SetKind(kind livekit.ParticipantInfo_Kind) *AccessToken {
|
||||
t.grant.SetParticipantKind(kind)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *AccessToken) AddGrant(grant *VideoGrant) *AccessToken {
|
||||
t.grant.Video = grant
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *AccessToken) AddSIPGrant(grant *SIPGrant) *AccessToken {
|
||||
t.grant.SIP = grant
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *AccessToken) SetMetadata(md string) *AccessToken {
|
||||
t.grant.Metadata = md
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *AccessToken) SetAttributes(attrs map[string]string) *AccessToken {
|
||||
if len(attrs) == 0 {
|
||||
return t
|
||||
}
|
||||
if t.grant.Attributes == nil {
|
||||
t.grant.Attributes = make(map[string]string)
|
||||
}
|
||||
for k, v := range attrs {
|
||||
t.grant.Attributes[k] = v
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *AccessToken) SetSha256(sha string) *AccessToken {
|
||||
t.grant.Sha256 = sha
|
||||
return t
|
||||
}
|
||||
|
||||
@@ -68,14 +115,7 @@ func (t *AccessToken) ToJWT() (string, error) {
|
||||
Issuer: t.apiKey,
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
Expiry: jwt.NewNumericDate(time.Now().Add(validFor)),
|
||||
ID: t.identity,
|
||||
Subject: t.grant.Identity,
|
||||
}
|
||||
grants := &ClaimGrants{}
|
||||
if t.videoGrant != nil {
|
||||
grants.Video = t.videoGrant
|
||||
}
|
||||
if t.metadata != nil {
|
||||
grants.Metadata = t.metadata
|
||||
}
|
||||
return jwt.Signed(sig).Claims(cl).Claims(grants).CompactSerialize()
|
||||
return jwt.Signed(sig).Claims(cl).Claims(&t.grant).CompactSerialize()
|
||||
}
|
||||
|
||||
@@ -1,69 +1,108 @@
|
||||
package auth_test
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
"github.com/go-jose/go-jose/v3/jwt"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/guid"
|
||||
)
|
||||
|
||||
func TestAccessToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("keys must be set", func(t *testing.T) {
|
||||
token := auth.NewAccessToken("", "")
|
||||
token := NewAccessToken("", "")
|
||||
_, err := token.ToJWT()
|
||||
assert.Equal(t, auth.ErrKeysMissing, err)
|
||||
require.Equal(t, ErrKeysMissing, err)
|
||||
})
|
||||
|
||||
t.Run("generates a decodeable key", func(t *testing.T) {
|
||||
t.Run("generates a decode-able key", func(t *testing.T) {
|
||||
apiKey, secret := apiKeypair()
|
||||
videoGrant := &auth.VideoGrant{RoomJoin: true, Room: "myroom"}
|
||||
at := auth.NewAccessToken(apiKey, secret).
|
||||
videoGrant := &VideoGrant{RoomJoin: true, Room: "myroom"}
|
||||
sipGrant := &SIPGrant{Admin: true}
|
||||
at := NewAccessToken(apiKey, secret).
|
||||
AddGrant(videoGrant).
|
||||
AddSIPGrant(sipGrant).
|
||||
SetValidFor(time.Minute * 5).
|
||||
SetKind(livekit.ParticipantInfo_AGENT).
|
||||
SetIdentity("user")
|
||||
value, err := at.ToJWT()
|
||||
//fmt.Println(raw)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, strings.Split(value, "."), 3)
|
||||
require.Len(t, strings.Split(value, "."), 3)
|
||||
|
||||
// ensure it's a valid JWT
|
||||
token, err := jwt.ParseSigned(value)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedGrant := auth.ClaimGrants{}
|
||||
decodedGrant := ClaimGrants{}
|
||||
err = token.UnsafeClaimsWithoutVerification(&decodedGrant)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, videoGrant, decodedGrant.Video)
|
||||
require.EqualValues(t, livekit.ParticipantInfo_AGENT, decodedGrant.GetParticipantKind())
|
||||
require.EqualValues(t, videoGrant, decodedGrant.Video)
|
||||
require.EqualValues(t, sipGrant, decodedGrant.SIP)
|
||||
})
|
||||
|
||||
t.Run("missing kind should be interpreted as standard", func(t *testing.T) {
|
||||
apiKey, secret := apiKeypair()
|
||||
value, err := NewAccessToken(apiKey, secret).
|
||||
AddGrant(&VideoGrant{RoomJoin: true, Room: "myroom"}).
|
||||
ToJWT()
|
||||
require.NoError(t, err)
|
||||
token, err := jwt.ParseSigned(value)
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedGrant := ClaimGrants{}
|
||||
err = token.UnsafeClaimsWithoutVerification(&decodedGrant)
|
||||
require.NoError(t, err)
|
||||
|
||||
// default validity
|
||||
require.EqualValues(t, livekit.ParticipantInfo_STANDARD, decodedGrant.GetParticipantKind())
|
||||
})
|
||||
|
||||
t.Run("default validity should be more than a minute", func(t *testing.T) {
|
||||
apiKey, secret := apiKeypair()
|
||||
videoGrant := &auth.VideoGrant{RoomJoin: true, Room: "myroom"}
|
||||
at := auth.NewAccessToken(apiKey, secret).
|
||||
videoGrant := &VideoGrant{RoomJoin: true, Room: "myroom"}
|
||||
at := NewAccessToken(apiKey, secret).
|
||||
AddGrant(videoGrant)
|
||||
value, err := at.ToJWT()
|
||||
require.NoError(t, err)
|
||||
token, err := jwt.ParseSigned(value)
|
||||
require.NoError(t, err)
|
||||
|
||||
claim := jwt.Claims{}
|
||||
decodedGrant := auth.ClaimGrants{}
|
||||
decodedGrant := ClaimGrants{}
|
||||
err = token.UnsafeClaimsWithoutVerification(&claim, &decodedGrant)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, videoGrant, decodedGrant.Video)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, videoGrant, decodedGrant.Video)
|
||||
|
||||
// default validity
|
||||
assert.True(t, claim.Expiry.Time().Sub(claim.IssuedAt.Time()) > time.Minute)
|
||||
require.True(t, claim.Expiry.Time().Sub(claim.IssuedAt.Time()) > time.Minute)
|
||||
})
|
||||
}
|
||||
|
||||
func apiKeypair() (string, string) {
|
||||
return utils.NewGuid(utils.APIKeyPrefix), utils.RandomSecret()
|
||||
return guid.New(utils.APIKeyPrefix), utils.RandomSecret()
|
||||
}
|
||||
|
||||
315
auth/grants.go
315
auth/grants.go
@@ -1,18 +1,327 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
type VideoGrant struct {
|
||||
// actions on rooms
|
||||
RoomCreate bool `json:"roomCreate,omitempty"`
|
||||
RoomList bool `json:"roomList,omitempty"`
|
||||
RoomRecord bool `json:"roomRecord,omitempty"`
|
||||
|
||||
// actions on a particular room
|
||||
RoomAdmin bool `json:"roomAdmin,omitempty"`
|
||||
RoomJoin bool `json:"roomJoin,omitempty"`
|
||||
Room string `json:"room,omitempty"`
|
||||
|
||||
// permissions within a room, if none of the permissions are set explicitly
|
||||
// it will be granted with all publish and subscribe permissions
|
||||
CanPublish *bool `json:"canPublish,omitempty"`
|
||||
CanSubscribe *bool `json:"canSubscribe,omitempty"`
|
||||
CanPublishData *bool `json:"canPublishData,omitempty"`
|
||||
// TrackSource types that a participant may publish.
|
||||
// When set, it supersedes CanPublish. Only sources explicitly set here can be published
|
||||
CanPublishSources []string `json:"canPublishSources,omitempty"` // keys keep track of each source
|
||||
// by default, a participant is not allowed to update its own metadata
|
||||
CanUpdateOwnMetadata *bool `json:"canUpdateOwnMetadata,omitempty"`
|
||||
|
||||
// actions on ingresses
|
||||
IngressAdmin bool `json:"ingressAdmin,omitempty"` // applies to all ingress
|
||||
|
||||
// participant is not visible to other participants
|
||||
Hidden bool `json:"hidden,omitempty"`
|
||||
// indicates to the room that current participant is a recorder
|
||||
Recorder bool `json:"recorder,omitempty"`
|
||||
// indicates that the holder can register as an Agent framework worker
|
||||
// it is also set on all participants that are joining as Agent
|
||||
Agent bool `json:"agent,omitempty"`
|
||||
}
|
||||
|
||||
type SIPGrant struct {
|
||||
// Admin grants access to all SIP features.
|
||||
Admin bool `json:"admin,omitempty"`
|
||||
|
||||
// Call allows making outbound SIP calls.
|
||||
Call bool `json:"call,omitempty"`
|
||||
}
|
||||
|
||||
type ClaimGrants struct {
|
||||
Identity string `json:"-"`
|
||||
Video *VideoGrant `json:"video,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
Identity string `json:"-"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
Video *VideoGrant `json:"video,omitempty"`
|
||||
SIP *SIPGrant `json:"sip,omitempty"`
|
||||
// for verifying integrity of the message body
|
||||
Sha256 string `json:"sha256,omitempty"`
|
||||
Metadata string `json:"metadata,omitempty"`
|
||||
// Key/value attributes to attach to the participant
|
||||
Attributes map[string]string `json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ClaimGrants) SetParticipantKind(kind livekit.ParticipantInfo_Kind) {
|
||||
c.Kind = kindFromProto(kind)
|
||||
}
|
||||
|
||||
func (c *ClaimGrants) GetParticipantKind() livekit.ParticipantInfo_Kind {
|
||||
return kindToProto(c.Kind)
|
||||
}
|
||||
|
||||
func (c *ClaimGrants) Clone() *ClaimGrants {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
clone := *c
|
||||
clone.Video = c.Video.Clone()
|
||||
clone.SIP = c.SIP.Clone()
|
||||
clone.Attributes = maps.Clone(c.Attributes)
|
||||
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (v *VideoGrant) SetCanPublish(val bool) {
|
||||
v.CanPublish = &val
|
||||
}
|
||||
|
||||
func (v *VideoGrant) SetCanPublishData(val bool) {
|
||||
v.CanPublishData = &val
|
||||
}
|
||||
|
||||
func (v *VideoGrant) SetCanSubscribe(val bool) {
|
||||
v.CanSubscribe = &val
|
||||
}
|
||||
|
||||
func (v *VideoGrant) SetCanPublishSources(sources []livekit.TrackSource) {
|
||||
v.CanPublishSources = make([]string, 0, len(sources))
|
||||
for _, s := range sources {
|
||||
v.CanPublishSources = append(v.CanPublishSources, sourceToString(s))
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VideoGrant) SetCanUpdateOwnMetadata(val bool) {
|
||||
v.CanUpdateOwnMetadata = &val
|
||||
}
|
||||
|
||||
func (v *VideoGrant) GetCanPublish() bool {
|
||||
if v.CanPublish == nil {
|
||||
return true
|
||||
}
|
||||
return *v.CanPublish
|
||||
}
|
||||
|
||||
func (v *VideoGrant) GetCanPublishSource(source livekit.TrackSource) bool {
|
||||
if !v.GetCanPublish() {
|
||||
return false
|
||||
}
|
||||
// don't differentiate between nil and unset, since that distinction doesn't survive serialization
|
||||
if len(v.CanPublishSources) == 0 {
|
||||
return true
|
||||
}
|
||||
sourceStr := sourceToString(source)
|
||||
for _, s := range v.CanPublishSources {
|
||||
if s == sourceStr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (v *VideoGrant) GetCanPublishSources() []livekit.TrackSource {
|
||||
if len(v.CanPublishSources) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sources := make([]livekit.TrackSource, 0, len(v.CanPublishSources))
|
||||
for _, s := range v.CanPublishSources {
|
||||
sources = append(sources, sourceToProto(s))
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
func (v *VideoGrant) GetCanPublishData() bool {
|
||||
if v.CanPublishData == nil {
|
||||
return v.GetCanPublish()
|
||||
}
|
||||
return *v.CanPublishData
|
||||
}
|
||||
|
||||
func (v *VideoGrant) GetCanSubscribe() bool {
|
||||
if v.CanSubscribe == nil {
|
||||
return true
|
||||
}
|
||||
return *v.CanSubscribe
|
||||
}
|
||||
|
||||
func (v *VideoGrant) GetCanUpdateOwnMetadata() bool {
|
||||
if v.CanUpdateOwnMetadata == nil {
|
||||
return false
|
||||
}
|
||||
return *v.CanUpdateOwnMetadata
|
||||
}
|
||||
|
||||
func (v *VideoGrant) MatchesPermission(permission *livekit.ParticipantPermission) bool {
|
||||
if permission == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.GetCanPublish() != permission.CanPublish {
|
||||
return false
|
||||
}
|
||||
if v.GetCanPublishData() != permission.CanPublishData {
|
||||
return false
|
||||
}
|
||||
if v.GetCanSubscribe() != permission.CanSubscribe {
|
||||
return false
|
||||
}
|
||||
if v.GetCanUpdateOwnMetadata() != permission.CanUpdateMetadata {
|
||||
return false
|
||||
}
|
||||
if v.Hidden != permission.Hidden {
|
||||
return false
|
||||
}
|
||||
if v.Recorder != permission.Recorder {
|
||||
return false
|
||||
}
|
||||
if v.Agent != permission.Agent {
|
||||
return false
|
||||
}
|
||||
if !slices.Equal(v.GetCanPublishSources(), permission.CanPublishSources) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (v *VideoGrant) UpdateFromPermission(permission *livekit.ParticipantPermission) {
|
||||
if permission == nil {
|
||||
return
|
||||
}
|
||||
|
||||
v.SetCanPublish(permission.CanPublish)
|
||||
v.SetCanPublishData(permission.CanPublishData)
|
||||
v.SetCanPublishSources(permission.CanPublishSources)
|
||||
v.SetCanSubscribe(permission.CanSubscribe)
|
||||
v.SetCanUpdateOwnMetadata(permission.CanUpdateMetadata)
|
||||
v.Hidden = permission.Hidden
|
||||
v.Recorder = permission.Recorder
|
||||
v.Agent = permission.Agent
|
||||
}
|
||||
|
||||
func (v *VideoGrant) ToPermission() *livekit.ParticipantPermission {
|
||||
pp := &livekit.ParticipantPermission{
|
||||
CanPublish: v.GetCanPublish(),
|
||||
CanPublishData: v.GetCanPublishData(),
|
||||
CanSubscribe: v.GetCanSubscribe(),
|
||||
CanPublishSources: v.GetCanPublishSources(),
|
||||
CanUpdateMetadata: v.GetCanUpdateOwnMetadata(),
|
||||
Hidden: v.Hidden,
|
||||
Recorder: v.Recorder,
|
||||
Agent: v.Agent,
|
||||
}
|
||||
return pp
|
||||
}
|
||||
|
||||
func (v *VideoGrant) Clone() *VideoGrant {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
clone := *v
|
||||
|
||||
if v.CanPublish != nil {
|
||||
canPublish := *v.CanPublish
|
||||
clone.CanPublish = &canPublish
|
||||
}
|
||||
|
||||
if v.CanSubscribe != nil {
|
||||
canSubscribe := *v.CanSubscribe
|
||||
clone.CanSubscribe = &canSubscribe
|
||||
}
|
||||
|
||||
if v.CanPublishData != nil {
|
||||
canPublishData := *v.CanPublishData
|
||||
clone.CanPublishData = &canPublishData
|
||||
}
|
||||
|
||||
if v.CanPublishSources != nil {
|
||||
clone.CanPublishSources = make([]string, len(v.CanPublishSources))
|
||||
copy(clone.CanPublishSources, v.CanPublishSources)
|
||||
}
|
||||
|
||||
if v.CanUpdateOwnMetadata != nil {
|
||||
canUpdateOwnMetadata := *v.CanUpdateOwnMetadata
|
||||
clone.CanUpdateOwnMetadata = &canUpdateOwnMetadata
|
||||
}
|
||||
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (s *SIPGrant) Clone() *SIPGrant {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
clone := *s
|
||||
|
||||
return &clone
|
||||
}
|
||||
|
||||
func sourceToString(source livekit.TrackSource) string {
|
||||
return strings.ToLower(source.String())
|
||||
}
|
||||
|
||||
func sourceToProto(sourceStr string) livekit.TrackSource {
|
||||
switch sourceStr {
|
||||
case "camera":
|
||||
return livekit.TrackSource_CAMERA
|
||||
case "microphone":
|
||||
return livekit.TrackSource_MICROPHONE
|
||||
case "screen_share":
|
||||
return livekit.TrackSource_SCREEN_SHARE
|
||||
case "screen_share_audio":
|
||||
return livekit.TrackSource_SCREEN_SHARE_AUDIO
|
||||
default:
|
||||
return livekit.TrackSource_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
func kindFromProto(source livekit.ParticipantInfo_Kind) string {
|
||||
return strings.ToLower(source.String())
|
||||
}
|
||||
|
||||
func kindToProto(sourceStr string) livekit.ParticipantInfo_Kind {
|
||||
switch sourceStr {
|
||||
case "", "standard":
|
||||
return livekit.ParticipantInfo_STANDARD
|
||||
case "ingress":
|
||||
return livekit.ParticipantInfo_INGRESS
|
||||
case "egress":
|
||||
return livekit.ParticipantInfo_EGRESS
|
||||
case "sip":
|
||||
return livekit.ParticipantInfo_SIP
|
||||
case "agent":
|
||||
return livekit.ParticipantInfo_AGENT
|
||||
default:
|
||||
return livekit.ParticipantInfo_STANDARD
|
||||
}
|
||||
}
|
||||
|
||||
100
auth/grants_test.go
Normal file
100
auth/grants_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
func TestGrants(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("clone default grant", func(t *testing.T) {
|
||||
grants := &ClaimGrants{}
|
||||
clone := grants.Clone()
|
||||
require.NotSame(t, grants, clone)
|
||||
require.Same(t, grants.Video, clone.Video)
|
||||
require.True(t, reflect.DeepEqual(grants, clone))
|
||||
require.True(t, reflect.DeepEqual(grants.Video, clone.Video))
|
||||
})
|
||||
|
||||
t.Run("clone nil video", func(t *testing.T) {
|
||||
grants := &ClaimGrants{
|
||||
Identity: "identity",
|
||||
Name: "name",
|
||||
Sha256: "sha256",
|
||||
Metadata: "metadata",
|
||||
}
|
||||
clone := grants.Clone()
|
||||
require.NotSame(t, grants, clone)
|
||||
require.Same(t, grants.Video, clone.Video)
|
||||
require.True(t, reflect.DeepEqual(grants, clone))
|
||||
require.True(t, reflect.DeepEqual(grants.Video, clone.Video))
|
||||
})
|
||||
|
||||
t.Run("clone with video", func(t *testing.T) {
|
||||
tr := true
|
||||
fa := false
|
||||
video := &VideoGrant{
|
||||
RoomCreate: true,
|
||||
RoomList: false,
|
||||
RoomRecord: true,
|
||||
RoomAdmin: false,
|
||||
RoomJoin: true,
|
||||
Room: "room",
|
||||
CanPublish: &tr,
|
||||
CanSubscribe: &fa,
|
||||
CanPublishData: nil,
|
||||
Hidden: true,
|
||||
Recorder: false,
|
||||
}
|
||||
grants := &ClaimGrants{
|
||||
Identity: "identity",
|
||||
Name: "name",
|
||||
Kind: "kind",
|
||||
Video: video,
|
||||
Sha256: "sha256",
|
||||
Metadata: "metadata",
|
||||
}
|
||||
clone := grants.Clone()
|
||||
require.NotSame(t, grants, clone)
|
||||
require.NotSame(t, grants.Video, clone.Video)
|
||||
require.NotSame(t, grants.Video.CanPublish, clone.Video.CanPublish)
|
||||
require.NotSame(t, grants.Video.CanSubscribe, clone.Video.CanSubscribe)
|
||||
require.Same(t, grants.Video.CanPublishData, clone.Video.CanPublishData)
|
||||
require.True(t, reflect.DeepEqual(grants, clone))
|
||||
require.True(t, reflect.DeepEqual(grants.Video, clone.Video))
|
||||
})
|
||||
}
|
||||
|
||||
func TestParticipantKind(t *testing.T) {
|
||||
const kindMin, kindMax = livekit.ParticipantInfo_STANDARD, livekit.ParticipantInfo_AGENT
|
||||
for k := kindMin; k <= kindMax; k++ {
|
||||
k := k
|
||||
t.Run(k.String(), func(t *testing.T) {
|
||||
require.Equal(t, k, kindToProto(kindFromProto(k)))
|
||||
})
|
||||
}
|
||||
const kindNext = kindMax + 1
|
||||
if _, err := strconv.Atoi(kindNext.String()); err != nil {
|
||||
t.Errorf("Please update kindMax to match protobuf. Missing value: %s", kindNext)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
@@ -36,3 +50,26 @@ func (p *FileBasedKeyProvider) GetSecret(key string) string {
|
||||
func (p *FileBasedKeyProvider) NumKeys() int {
|
||||
return len(p.keys)
|
||||
}
|
||||
|
||||
type SimpleKeyProvider struct {
|
||||
apiKey string
|
||||
apiSecret string
|
||||
}
|
||||
|
||||
func NewSimpleKeyProvider(apiKey, apiSecret string) *SimpleKeyProvider {
|
||||
return &SimpleKeyProvider{
|
||||
apiKey: apiKey,
|
||||
apiSecret: apiSecret,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *SimpleKeyProvider) GetSecret(key string) string {
|
||||
if key == p.apiKey {
|
||||
return p.apiSecret
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *SimpleKeyProvider) NumKeys() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/livekit/protocol/auth"
|
||||
)
|
||||
@@ -16,7 +30,7 @@ func TestFileBasedKeyProvider(t *testing.T) {
|
||||
"key2": "secret2",
|
||||
"key3": "secret3",
|
||||
}
|
||||
f, err := ioutil.TempFile("", "keyfile")
|
||||
f, err := os.CreateTemp("", "keyfile")
|
||||
assert.NoError(t, err)
|
||||
defer func() {
|
||||
os.Remove(f.Name())
|
||||
@@ -30,6 +44,7 @@ func TestFileBasedKeyProvider(t *testing.T) {
|
||||
f.Close()
|
||||
|
||||
r, err := os.Open(f.Name())
|
||||
require.NoError(t, err)
|
||||
defer r.Close()
|
||||
p, err := auth.NewFileBasedKeyProviderFromReader(r)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
"github.com/go-jose/go-jose/v3/jwt"
|
||||
)
|
||||
|
||||
type APIKeyTokenVerifier struct {
|
||||
@@ -12,6 +26,7 @@ type APIKeyTokenVerifier struct {
|
||||
apiKey string
|
||||
}
|
||||
|
||||
// ParseAPIToken parses an encoded JWT token and
|
||||
func ParseAPIToken(raw string) (*APIKeyTokenVerifier, error) {
|
||||
tok, err := jwt.ParseSigned(raw)
|
||||
if err != nil {
|
||||
@@ -23,14 +38,18 @@ func ParseAPIToken(raw string) (*APIKeyTokenVerifier, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &APIKeyTokenVerifier{
|
||||
v := &APIKeyTokenVerifier{
|
||||
token: tok,
|
||||
apiKey: out.Issuer,
|
||||
identity: out.ID,
|
||||
}, nil
|
||||
identity: out.Subject,
|
||||
}
|
||||
if v.identity == "" {
|
||||
v.identity = out.ID
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Returns the API key this token was signed with
|
||||
// APIKey returns the API key this token was signed with
|
||||
func (v *APIKeyTokenVerifier) APIKey() string {
|
||||
return v.apiKey
|
||||
}
|
||||
@@ -56,6 +75,6 @@ func (v *APIKeyTokenVerifier) Verify(key interface{}) (*ClaimGrants, error) {
|
||||
}
|
||||
|
||||
// copy over identity
|
||||
claims.Identity = out.ID
|
||||
claims.Identity = v.identity
|
||||
return &claims, nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,25 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/go-jose/go-jose/v3/json"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/livekit/protocol/auth"
|
||||
)
|
||||
@@ -15,21 +30,22 @@ func TestVerifier(t *testing.T) {
|
||||
accessToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDg5MzAzMDgsImlzcyI6IkFQSUQzQjY3dXhrNE5qMkdLaVJQaWJBWjkiLCJuYmYiOjE2MDg5MjY3MDgsInJvb21fam9pbiI6dHJ1ZSwicm9vbV9zaWQiOiJteWlkIiwic3ViIjoiQVBJRDNCNjd1eGs0TmoyR0tpUlBpYkFaOSJ9.cmHEBq0MLyRqphmVLM2cLXg5ao5Sro7am8yXhcYKcwE"
|
||||
t.Run("cannot decode with incorrect key", func(t *testing.T) {
|
||||
v, err := auth.ParseAPIToken(accessToken)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, apiKey, v.APIKey())
|
||||
require.Equal(t, apiKey, v.APIKey())
|
||||
_, err = v.Verify("")
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = v.Verify("anothersecret")
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("key has expired", func(t *testing.T) {
|
||||
v, err := auth.ParseAPIToken(accessToken)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = v.Verify(secret)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("unexpired token is verified", func(t *testing.T) {
|
||||
@@ -39,16 +55,16 @@ func TestVerifier(t *testing.T) {
|
||||
SetValidFor(time.Minute).
|
||||
SetIdentity("me")
|
||||
authToken, err := at.ToJWT()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
v, err := auth.ParseAPIToken(authToken)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, apiKey, v.APIKey())
|
||||
assert.Equal(t, "me", v.Identity())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, apiKey, v.APIKey())
|
||||
require.Equal(t, "me", v.Identity())
|
||||
|
||||
decoded, err := v.Verify(secret)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &claim, decoded.Video)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &claim, decoded.Video)
|
||||
})
|
||||
|
||||
t.Run("ensure metadata can be passed through", func(t *testing.T) {
|
||||
@@ -56,22 +72,47 @@ func TestVerifier(t *testing.T) {
|
||||
"user": "value",
|
||||
"number": float64(3),
|
||||
}
|
||||
md, _ := json.Marshal(metadata)
|
||||
attrs := map[string]string{"mykey": "myval", "secondkey": "secondval"}
|
||||
at := auth.NewAccessToken(apiKey, secret).
|
||||
AddGrant(&auth.VideoGrant{
|
||||
RoomAdmin: true,
|
||||
Room: "myroom",
|
||||
}).
|
||||
SetMetadata(metadata)
|
||||
SetMetadata(string(md)).
|
||||
SetAttributes(attrs)
|
||||
|
||||
authToken, err := at.ToJWT()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
v, err := auth.ParseAPIToken(authToken)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
decoded, err := v.Verify(secret)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, metadata, decoded.Metadata)
|
||||
require.EqualValues(t, string(md), decoded.Metadata)
|
||||
require.EqualValues(t, attrs, decoded.Attributes)
|
||||
})
|
||||
|
||||
t.Run("nil permissions are handled", func(t *testing.T) {
|
||||
grant := &auth.VideoGrant{
|
||||
Room: "myroom",
|
||||
RoomJoin: true,
|
||||
}
|
||||
grant.SetCanPublishData(false)
|
||||
at := auth.NewAccessToken(apiKey, secret).
|
||||
AddGrant(grant)
|
||||
token, err := at.ToJWT()
|
||||
require.NoError(t, err)
|
||||
|
||||
v, err := auth.ParseAPIToken(token)
|
||||
require.NoError(t, err)
|
||||
decoded, err := v.Verify(secret)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Nil(t, decoded.Video.CanSubscribe)
|
||||
require.Nil(t, decoded.Video.CanPublish)
|
||||
require.False(t, *decoded.Video.CanPublishData)
|
||||
})
|
||||
}
|
||||
|
||||
57
bootstrap.sh
Executable file
57
bootstrap.sh
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2023 LiveKit, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
if ! command -v protoc &> /dev/null
|
||||
then
|
||||
echo "protoc is required and not found. please install"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v mage &> /dev/null
|
||||
then
|
||||
pushd /tmp
|
||||
git clone https://github.com/magefile/mage
|
||||
cd mage
|
||||
go run bootstrap.go
|
||||
rm -rf /tmp/mage
|
||||
popd
|
||||
fi
|
||||
|
||||
if ! command -v mage &> /dev/null
|
||||
then
|
||||
echo "Ensure `go env GOPATH`/bin is in your \$PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
go mod download
|
||||
|
||||
GO_VERSION=`go version | { read _ _ v _; echo ${v#go}; }`
|
||||
GO_TARGET_VERSION=1.17
|
||||
|
||||
function version { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; }
|
||||
|
||||
if [ $(version $GO_VERSION) -ge $(version $GO_TARGET_VERSION) ];
|
||||
then
|
||||
go install github.com/twitchtv/twirp/protoc-gen-twirp@v8.1.3
|
||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3
|
||||
go install github.com/livekit/psrpc/protoc-gen-psrpc@v0.5.1
|
||||
else
|
||||
go get -u github.com/twitchtv/twirp/protoc-gen-twirp@v8.1.3
|
||||
go get -u google.golang.org/protobuf/cmd/protoc-gen-go@v1.30.0
|
||||
go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
|
||||
go get -u github.com/livekit/psrpc/protoc-gen-psrpc@v0.3.1
|
||||
fi
|
||||
24
egress/errors.go
Normal file
24
egress/errors.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package egress
|
||||
|
||||
import (
|
||||
"github.com/livekit/psrpc"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoResponse = psrpc.NewErrorf(psrpc.Unavailable, "no response from egress service")
|
||||
ErrEgressTimedOut = psrpc.NewErrorf(psrpc.DeadlineExceeded, "egress timed out")
|
||||
)
|
||||
75
egress/redact.go
Normal file
75
egress/redact.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package egress
|
||||
|
||||
import (
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/utils"
|
||||
)
|
||||
|
||||
func RedactUpload(req UploadRequest) {
|
||||
if s3 := req.GetS3(); s3 != nil {
|
||||
s3.AccessKey = utils.Redact(s3.AccessKey, "{access_key}")
|
||||
s3.Secret = utils.Redact(s3.Secret, "{secret}")
|
||||
return
|
||||
}
|
||||
|
||||
if gcp := req.GetGcp(); gcp != nil {
|
||||
gcp.Credentials = utils.Redact(gcp.Credentials, "{credentials}")
|
||||
return
|
||||
}
|
||||
|
||||
if azure := req.GetAzure(); azure != nil {
|
||||
azure.AccountName = utils.Redact(azure.AccountName, "{account_name}")
|
||||
azure.AccountKey = utils.Redact(azure.AccountKey, "{account_key}")
|
||||
return
|
||||
}
|
||||
|
||||
if aliOSS := req.GetAliOSS(); aliOSS != nil {
|
||||
aliOSS.AccessKey = utils.Redact(aliOSS.AccessKey, "{access_key}")
|
||||
aliOSS.Secret = utils.Redact(aliOSS.Secret, "{secret}")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func RedactAutoEncodedOutput(out AutoEncodedOutput) {
|
||||
if files := out.GetFileOutputs(); len(files) == 1 {
|
||||
RedactUpload(files[0])
|
||||
}
|
||||
if segments := out.GetSegmentOutputs(); len(segments) == 1 {
|
||||
RedactUpload(segments[0])
|
||||
}
|
||||
}
|
||||
|
||||
func RedactEncodedOutputs(out EncodedOutput) {
|
||||
RedactAutoEncodedOutput(out)
|
||||
|
||||
if streams := out.GetStreamOutputs(); len(streams) == 1 {
|
||||
RedactStreamKeys(streams[0])
|
||||
}
|
||||
if images := out.GetImageOutputs(); len(images) == 1 {
|
||||
RedactUpload(images[0])
|
||||
}
|
||||
|
||||
if o, ok := out.(EncodedOutputDeprecated); ok {
|
||||
if file := o.GetFile(); file != nil {
|
||||
RedactUpload(file)
|
||||
} else if stream := o.GetStream(); stream != nil {
|
||||
RedactStreamKeys(stream)
|
||||
} else if segment := o.GetSegments(); segment != nil {
|
||||
RedactUpload(segment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RedactDirectOutputs(out DirectOutput) {
|
||||
if f := out.GetFile(); f != nil {
|
||||
RedactUpload(f)
|
||||
}
|
||||
}
|
||||
|
||||
func RedactStreamKeys(stream *livekit.StreamOutput) {
|
||||
for i, url := range stream.Urls {
|
||||
if redacted, ok := utils.RedactStreamKey(url); ok {
|
||||
stream.Urls[i] = redacted
|
||||
}
|
||||
}
|
||||
}
|
||||
139
egress/redact_test.go
Normal file
139
egress/redact_test.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package egress
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
file = &livekit.EncodedFileOutput{
|
||||
Output: &livekit.EncodedFileOutput_S3{
|
||||
S3: &livekit.S3Upload{
|
||||
AccessKey: "ACCESS_KEY",
|
||||
Secret: "LONG_SECRET_STRING",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
image = &livekit.ImageOutput{
|
||||
Output: &livekit.ImageOutput_AliOSS{
|
||||
AliOSS: &livekit.AliOSSUpload{
|
||||
AccessKey: "ACCESS_KEY",
|
||||
Secret: "LONG_SECRET_STRING",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
segments = &livekit.SegmentedFileOutput{
|
||||
Output: &livekit.SegmentedFileOutput_Gcp{
|
||||
Gcp: &livekit.GCPUpload{
|
||||
Credentials: "CREDENTIALS",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
directFile = &livekit.DirectFileOutput{
|
||||
Output: &livekit.DirectFileOutput_Azure{
|
||||
Azure: &livekit.AzureBlobUpload{
|
||||
AccountName: "ACCOUNT_NAME",
|
||||
AccountKey: "ACCOUNT_KEY",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestRedactUpload(t *testing.T) {
|
||||
cl := proto.Clone(file)
|
||||
RedactUpload(cl.(UploadRequest))
|
||||
|
||||
require.Equal(t, "{access_key}", cl.(*livekit.EncodedFileOutput).Output.(*livekit.EncodedFileOutput_S3).S3.AccessKey)
|
||||
require.Equal(t, "{secret}", cl.(*livekit.EncodedFileOutput).Output.(*livekit.EncodedFileOutput_S3).S3.Secret)
|
||||
|
||||
cl = proto.Clone(image)
|
||||
RedactUpload(cl.(UploadRequest))
|
||||
|
||||
require.Equal(t, "{access_key}", cl.(*livekit.ImageOutput).Output.(*livekit.ImageOutput_AliOSS).AliOSS.AccessKey)
|
||||
require.Equal(t, "{secret}", cl.(*livekit.ImageOutput).Output.(*livekit.ImageOutput_AliOSS).AliOSS.Secret)
|
||||
|
||||
cl = proto.Clone(segments)
|
||||
RedactUpload(cl.(UploadRequest))
|
||||
|
||||
require.Equal(t, "{credentials}", cl.(*livekit.SegmentedFileOutput).Output.(*livekit.SegmentedFileOutput_Gcp).Gcp.Credentials)
|
||||
|
||||
cl = proto.Clone(directFile)
|
||||
RedactUpload(cl.(UploadRequest))
|
||||
|
||||
require.Equal(t, "{account_name}", cl.(*livekit.DirectFileOutput).Output.(*livekit.DirectFileOutput_Azure).Azure.AccountName)
|
||||
require.Equal(t, "{account_key}", cl.(*livekit.DirectFileOutput).Output.(*livekit.DirectFileOutput_Azure).Azure.AccountKey)
|
||||
}
|
||||
|
||||
func TestRedactStreamOutput(t *testing.T) {
|
||||
so := &livekit.StreamOutput{
|
||||
Urls: []string{
|
||||
"rtmps://foo.bar.com/app/secret_stream_key",
|
||||
},
|
||||
}
|
||||
|
||||
RedactStreamKeys(so)
|
||||
require.Equal(t, "rtmps://foo.bar.com/app/{sec...key}", so.Urls[0])
|
||||
}
|
||||
|
||||
func TestRedactEncodedOutputs(t *testing.T) {
|
||||
trackComposite := &livekit.TrackCompositeEgressRequest{
|
||||
FileOutputs: []*livekit.EncodedFileOutput{
|
||||
file,
|
||||
},
|
||||
}
|
||||
|
||||
cl := proto.Clone(trackComposite)
|
||||
RedactEncodedOutputs(cl.(EncodedOutput))
|
||||
|
||||
require.Equal(t, "{access_key}", cl.(*livekit.TrackCompositeEgressRequest).FileOutputs[0].Output.(*livekit.EncodedFileOutput_S3).S3.AccessKey)
|
||||
require.Equal(t, "{secret}", cl.(*livekit.TrackCompositeEgressRequest).FileOutputs[0].Output.(*livekit.EncodedFileOutput_S3).S3.Secret)
|
||||
|
||||
roomComposite := &livekit.RoomCompositeEgressRequest{
|
||||
ImageOutputs: []*livekit.ImageOutput{
|
||||
image,
|
||||
},
|
||||
}
|
||||
|
||||
cl = proto.Clone(roomComposite)
|
||||
RedactEncodedOutputs(cl.(EncodedOutput))
|
||||
|
||||
require.Equal(t, "{access_key}", cl.(*livekit.RoomCompositeEgressRequest).ImageOutputs[0].Output.(*livekit.ImageOutput_AliOSS).AliOSS.AccessKey)
|
||||
require.Equal(t, "{secret}", cl.(*livekit.RoomCompositeEgressRequest).ImageOutputs[0].Output.(*livekit.ImageOutput_AliOSS).AliOSS.Secret)
|
||||
|
||||
participant := &livekit.ParticipantEgressRequest{
|
||||
SegmentOutputs: []*livekit.SegmentedFileOutput{
|
||||
segments,
|
||||
},
|
||||
}
|
||||
|
||||
cl = proto.Clone(participant)
|
||||
RedactEncodedOutputs(cl.(EncodedOutput))
|
||||
|
||||
require.Equal(t, "{credentials}", cl.(*livekit.ParticipantEgressRequest).SegmentOutputs[0].Output.(*livekit.SegmentedFileOutput_Gcp).Gcp.Credentials)
|
||||
}
|
||||
|
||||
func TestRedactDirectOutput(t *testing.T) {
|
||||
track := &livekit.TrackEgressRequest{
|
||||
Output: &livekit.TrackEgressRequest_File{
|
||||
File: &livekit.DirectFileOutput{
|
||||
Output: &livekit.DirectFileOutput_S3{
|
||||
S3: &livekit.S3Upload{
|
||||
AccessKey: "ACCESS_KEY",
|
||||
Secret: "SECRET",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
RedactDirectOutputs(track)
|
||||
require.Equal(t, "{access_key}", track.Output.(*livekit.TrackEgressRequest_File).File.Output.(*livekit.DirectFileOutput_S3).S3.AccessKey)
|
||||
require.Equal(t, "{secret}", track.Output.(*livekit.TrackEgressRequest_File).File.Output.(*livekit.DirectFileOutput_S3).S3.Secret)
|
||||
}
|
||||
44
egress/token.go
Normal file
44
egress/token.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package egress
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
func BuildEgressToken(egressID, apiKey, secret, roomName string) (string, error) {
|
||||
f := false
|
||||
t := true
|
||||
grant := &auth.VideoGrant{
|
||||
RoomJoin: true,
|
||||
Room: roomName,
|
||||
CanSubscribe: &t,
|
||||
CanPublish: &f,
|
||||
CanPublishData: &f,
|
||||
Hidden: true,
|
||||
Recorder: true,
|
||||
}
|
||||
|
||||
at := auth.NewAccessToken(apiKey, secret).
|
||||
AddGrant(grant).
|
||||
SetIdentity(egressID).
|
||||
SetKind(livekit.ParticipantInfo_EGRESS).
|
||||
SetValidFor(24 * time.Hour)
|
||||
|
||||
return at.ToJWT()
|
||||
}
|
||||
132
egress/types.go
Normal file
132
egress/types.go
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package egress
|
||||
|
||||
import "github.com/livekit/protocol/livekit"
|
||||
|
||||
const (
|
||||
EgressTypeRoomComposite = "room_composite"
|
||||
EgressTypeWeb = "web"
|
||||
EgressTypeParticipant = "participant"
|
||||
EgressTypeTrackComposite = "track_composite"
|
||||
EgressTypeTrack = "track"
|
||||
|
||||
OutputTypeFile = "file"
|
||||
OutputTypeStream = "stream"
|
||||
OutputTypeSegments = "segments"
|
||||
OutputTypeImages = "images"
|
||||
OutputTypeMultiple = "multiple"
|
||||
|
||||
Unknown = "unknown"
|
||||
)
|
||||
|
||||
// Outputs that can be used in egress that are started automatically on room creation
|
||||
type AutoEncodedOutput interface {
|
||||
GetFileOutputs() []*livekit.EncodedFileOutput
|
||||
GetSegmentOutputs() []*livekit.SegmentedFileOutput
|
||||
}
|
||||
|
||||
type EncodedOutput interface {
|
||||
AutoEncodedOutput
|
||||
GetStreamOutputs() []*livekit.StreamOutput
|
||||
GetImageOutputs() []*livekit.ImageOutput
|
||||
}
|
||||
|
||||
type EncodedOutputDeprecated interface {
|
||||
GetFile() *livekit.EncodedFileOutput
|
||||
GetStream() *livekit.StreamOutput
|
||||
GetSegments() *livekit.SegmentedFileOutput
|
||||
}
|
||||
|
||||
type DirectOutput interface {
|
||||
GetFile() *livekit.DirectFileOutput
|
||||
GetWebsocketUrl() string
|
||||
}
|
||||
|
||||
type UploadRequest interface {
|
||||
GetS3() *livekit.S3Upload
|
||||
GetGcp() *livekit.GCPUpload
|
||||
GetAzure() *livekit.AzureBlobUpload
|
||||
GetAliOSS() *livekit.AliOSSUpload
|
||||
}
|
||||
|
||||
func GetTypes(request interface{}) (string, string) {
|
||||
switch req := request.(type) {
|
||||
case *livekit.EgressInfo_RoomComposite:
|
||||
return EgressTypeRoomComposite, GetOutputType(req.RoomComposite)
|
||||
|
||||
case *livekit.EgressInfo_Web:
|
||||
return EgressTypeWeb, GetOutputType(req.Web)
|
||||
|
||||
case *livekit.EgressInfo_Participant:
|
||||
return EgressTypeParticipant, GetOutputType(req.Participant)
|
||||
|
||||
case *livekit.EgressInfo_TrackComposite:
|
||||
return EgressTypeTrackComposite, GetOutputType(req.TrackComposite)
|
||||
|
||||
case *livekit.EgressInfo_Track:
|
||||
return EgressTypeTrack, GetOutputType(req.Track)
|
||||
}
|
||||
|
||||
return Unknown, Unknown
|
||||
}
|
||||
|
||||
func GetOutputType(req interface{}) string {
|
||||
if r, ok := req.(EncodedOutput); ok {
|
||||
outputs := make([]string, 0)
|
||||
if len(r.GetFileOutputs()) > 0 {
|
||||
outputs = append(outputs, OutputTypeFile)
|
||||
}
|
||||
if len(r.GetStreamOutputs()) > 0 {
|
||||
outputs = append(outputs, OutputTypeStream)
|
||||
}
|
||||
if len(r.GetSegmentOutputs()) > 0 {
|
||||
outputs = append(outputs, OutputTypeSegments)
|
||||
}
|
||||
if len(r.GetImageOutputs()) > 0 {
|
||||
outputs = append(outputs, OutputTypeImages)
|
||||
}
|
||||
|
||||
switch len(outputs) {
|
||||
default:
|
||||
return OutputTypeMultiple
|
||||
case 1:
|
||||
return outputs[0]
|
||||
case 0:
|
||||
if r, ok := req.(EncodedOutputDeprecated); ok {
|
||||
if r.GetFile() != nil {
|
||||
return OutputTypeFile
|
||||
}
|
||||
if r.GetStream() != nil {
|
||||
return OutputTypeStream
|
||||
}
|
||||
if r.GetSegments() != nil {
|
||||
return OutputTypeSegments
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r, ok := req.(DirectOutput); ok {
|
||||
if r.GetFile() != nil {
|
||||
return OutputTypeFile
|
||||
}
|
||||
if r.GetWebsocketUrl() != "" {
|
||||
return OutputTypeStream
|
||||
}
|
||||
}
|
||||
|
||||
return Unknown
|
||||
}
|
||||
81
egress/types_test.go
Normal file
81
egress/types_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package egress
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetOutputType(t *testing.T) {
|
||||
roomReq := &livekit.RoomCompositeEgressRequest{
|
||||
FileOutputs: []*livekit.EncodedFileOutput{
|
||||
&livekit.EncodedFileOutput{},
|
||||
},
|
||||
}
|
||||
|
||||
ot := GetOutputType(roomReq)
|
||||
require.Equal(t, OutputTypeFile, ot)
|
||||
|
||||
roomReq = &livekit.RoomCompositeEgressRequest{
|
||||
Output: &livekit.RoomCompositeEgressRequest_File{
|
||||
File: &livekit.EncodedFileOutput{},
|
||||
},
|
||||
}
|
||||
|
||||
ot = GetOutputType(roomReq)
|
||||
require.Equal(t, OutputTypeFile, ot)
|
||||
|
||||
trackReq := &livekit.TrackCompositeEgressRequest{
|
||||
SegmentOutputs: []*livekit.SegmentedFileOutput{
|
||||
&livekit.SegmentedFileOutput{},
|
||||
},
|
||||
}
|
||||
|
||||
ot = GetOutputType(trackReq)
|
||||
require.Equal(t, OutputTypeSegments, ot)
|
||||
|
||||
trackReq = &livekit.TrackCompositeEgressRequest{
|
||||
Output: &livekit.TrackCompositeEgressRequest_Segments{
|
||||
Segments: &livekit.SegmentedFileOutput{},
|
||||
},
|
||||
}
|
||||
|
||||
ot = GetOutputType(trackReq)
|
||||
require.Equal(t, OutputTypeSegments, ot)
|
||||
|
||||
webReq := &livekit.WebEgressRequest{
|
||||
StreamOutputs: []*livekit.StreamOutput{
|
||||
&livekit.StreamOutput{},
|
||||
},
|
||||
}
|
||||
|
||||
ot = GetOutputType(webReq)
|
||||
require.Equal(t, OutputTypeStream, ot)
|
||||
|
||||
webReq = &livekit.WebEgressRequest{
|
||||
Output: &livekit.WebEgressRequest_Stream{
|
||||
Stream: &livekit.StreamOutput{},
|
||||
},
|
||||
}
|
||||
|
||||
ot = GetOutputType(webReq)
|
||||
require.Equal(t, OutputTypeStream, ot)
|
||||
|
||||
participantReq := &livekit.ParticipantEgressRequest{
|
||||
ImageOutputs: []*livekit.ImageOutput{
|
||||
&livekit.ImageOutput{},
|
||||
},
|
||||
}
|
||||
|
||||
ot = GetOutputType(participantReq)
|
||||
require.Equal(t, OutputTypeImages, ot)
|
||||
|
||||
participantReq.SegmentOutputs = []*livekit.SegmentedFileOutput{
|
||||
&livekit.SegmentedFileOutput{},
|
||||
}
|
||||
|
||||
ot = GetOutputType(participantReq)
|
||||
require.Equal(t, OutputTypeMultiple, ot)
|
||||
|
||||
}
|
||||
91
go.mod
91
go.mod
@@ -1,14 +1,87 @@
|
||||
module github.com/livekit/protocol
|
||||
|
||||
go 1.16
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/lithammer/shortuuid/v3 v3.0.6
|
||||
github.com/lytics/base62 v0.0.0-20180808010106-0ee4de5a5d6d
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
github.com/benbjohnson/clock v1.3.5
|
||||
github.com/bufbuild/protoyaml-go v0.1.9
|
||||
github.com/eapache/channels v1.1.0
|
||||
github.com/frostbyte73/core v0.0.10
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/gammazero/deque v0.2.1
|
||||
github.com/go-jose/go-jose/v3 v3.0.3
|
||||
github.com/go-logr/logr v1.4.1
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7
|
||||
github.com/jxskiss/base62 v1.1.0
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1
|
||||
github.com/livekit/psrpc v0.5.3-0.20240526192918-fbdaf10e6aa5
|
||||
github.com/mackerelio/go-osstat v0.2.4
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1
|
||||
github.com/pion/logging v0.2.2
|
||||
github.com/pion/sdp/v3 v3.0.6
|
||||
github.com/pion/webrtc/v3 v3.2.28
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/prometheus/procfs v0.12.0
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0
|
||||
github.com/redis/go-redis/v9 v9.5.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible
|
||||
github.com/zeebo/xxh3 v1.0.2
|
||||
go.uber.org/atomic v1.11.0
|
||||
go.uber.org/multierr v1.11.0
|
||||
go.uber.org/zap v1.27.0
|
||||
go.uber.org/zap/exp v0.2.0
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8
|
||||
golang.org/x/mod v0.16.0
|
||||
google.golang.org/grpc v1.62.1
|
||||
google.golang.org/protobuf v1.33.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.33.0-20240401165935-b983156c5e99.1 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bufbuild/protovalidate-go v0.6.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/eapache/queue v1.1.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/cel-go v0.20.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/klauspost/compress v1.17.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/nats-io/nats.go v1.31.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.6 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/pion/datachannel v1.5.5 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.7 // indirect
|
||||
github.com/pion/ice/v2 v2.3.13 // indirect
|
||||
github.com/pion/interceptor v0.1.25 // indirect
|
||||
github.com/pion/mdns v0.0.12 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/rtcp v1.2.12 // indirect
|
||||
github.com/pion/rtp v1.8.3 // indirect
|
||||
github.com/pion/sctp v1.8.12 // indirect
|
||||
github.com/pion/srtp/v2 v2.0.18 // indirect
|
||||
github.com/pion/stun v0.6.1 // indirect
|
||||
github.com/pion/transport/v2 v2.2.3 // indirect
|
||||
github.com/pion/turn/v2 v2.1.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
|
||||
)
|
||||
|
||||
312
go.sum
312
go.sum
@@ -1,6 +1,47 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.33.0-20240401165935-b983156c5e99.1 h1:2IGhRovxlsOIQgx2ekZWo4wTPAYpck41+18ICxs37is=
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.33.0-20240401165935-b983156c5e99.1/go.mod h1:Tgn5bgL220vkFOI0KPStlcClPeOJzAv4uT+V8JXGUnw=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
||||
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bufbuild/protovalidate-go v0.6.1 h1:uzW8r0CDvqApUChNj87VzZVoQSKhcVdw5UWOE605UIw=
|
||||
github.com/bufbuild/protovalidate-go v0.6.1/go.mod h1:4BR3rKEJiUiTy+sqsusFn2ladOf0kYmA2Reo6BHSBgQ=
|
||||
github.com/bufbuild/protoyaml-go v0.1.9 h1:anV5UtF1Mlvkkgp4NWA6U/zOnJFng8Orq4Vf3ZUQHBU=
|
||||
github.com/bufbuild/protoyaml-go v0.1.9/go.mod h1:KCBItkvZOK/zwGueLdH1Wx1RLyFn5rCH7YjQrdty2Wc=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k=
|
||||
github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0=
|
||||
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/frostbyte73/core v0.0.10 h1:D4DQXdPb8ICayz0n75rs4UYTXrUSdxzUfeleuNJORsU=
|
||||
github.com/frostbyte73/core v0.0.10/go.mod h1:XsOGqrqe/VEV7+8vJ+3a8qnCIXNbKsoEiu/czs7nrcU=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
|
||||
github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU=
|
||||
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
|
||||
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
@@ -8,91 +49,294 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
|
||||
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
|
||||
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.6 h1:pr15YQyvhiSX/qPxncFtqk+v4xLEpOZObbsY/mKrcvA=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.6/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
|
||||
github.com/lytics/base62 v0.0.0-20180808010106-0ee4de5a5d6d h1:HONlVR8gkr5QibYNVFtj8ajfqRAKt21DmdzeZXZl6VQ=
|
||||
github.com/lytics/base62 v0.0.0-20180808010106-0ee4de5a5d6d/go.mod h1:nFZ1y9JiUDciefRL0X6OTobqQGgFCR+lbnn1lWsoQk0=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0 h1:8E6DrFvII6QR4eJ3PkFvV+lc03P+2qwqTPLm1ax7694=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0/go.mod h1:fcEyUyXZXoV4Abw8DX0t7wyL8mCDxXyU4iAFZfT3IHw=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1 h1:jm09419p0lqTkDaKb5iXdynYrzB84ErPPO4LbRASk58=
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1/go.mod h1:Rs3MhFwutWhGwmY1VQsygw28z5bWcnEYmS1OG9OxjOQ=
|
||||
github.com/livekit/psrpc v0.5.3-0.20240526192918-fbdaf10e6aa5 h1:mTZyrjk5WEWMsvaYtJ42pG7DuxysKj21DKPINpGSIto=
|
||||
github.com/livekit/psrpc v0.5.3-0.20240526192918-fbdaf10e6aa5/go.mod h1:CQUBSPfYYAaevg1TNCc6/aYsa8DJH4jSRFdCeSZk5u0=
|
||||
github.com/mackerelio/go-osstat v0.2.4 h1:qxGbdPkFo65PXOb/F/nhDKpF2nGmGaCFDLXoZjJTtUs=
|
||||
github.com/mackerelio/go-osstat v0.2.4/go.mod h1:Zy+qzGdZs3A9cuIqmgbJvwbmLQH9dJvtio5ZjJTbdlQ=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1 h1:NicmruxkeqHjDv03SfSxqmaLuisddudfP3h5wdXFbhM=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1/go.mod h1:eyp4DdUJAKkr9tvxR3jWhw2mDK7CWABMG5r9uyaKC7I=
|
||||
github.com/nats-io/nats.go v1.31.0 h1:/WFBHEc/dOKBF6qf1TZhrdEfTmOZ5JzdJ+Y3m6Y/p7E=
|
||||
github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8=
|
||||
github.com/nats-io/nkeys v0.4.6 h1:IzVe95ru2CT6ta874rt9saQRkWfe2nFj1NtvYSLqMzY=
|
||||
github.com/nats-io/nkeys v0.4.6/go.mod h1:4DxZNzenSVd1cYQoAa8948QY3QDjrHfcfVADymtkpts=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
|
||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||
github.com/pion/ice/v2 v2.3.13 h1:xOxP+4V9nSDlUaGFRf/LvAuGHDXRcjIdsbbXPK/w7c8=
|
||||
github.com/pion/ice/v2 v2.3.13/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw=
|
||||
github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc=
|
||||
github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8=
|
||||
github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||
github.com/pion/rtcp v1.2.12 h1:bKWiX93XKgDZENEXCijvHRU/wRifm6JV5DGcH6twtSM=
|
||||
github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
||||
github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||
github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8=
|
||||
github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||
github.com/pion/sctp v1.8.12 h1:2VX50pedElH+is6FI+OKyRTeN5oy4mrk2HjnGa3UCmY=
|
||||
github.com/pion/sctp v1.8.12/go.mod h1:cMLT45jqw3+jiJCrtHVwfQLnfR0MGZ4rgOJwUOIqLkI=
|
||||
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
|
||||
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||
github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo=
|
||||
github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA=
|
||||
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
||||
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
|
||||
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||
github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
|
||||
github.com/pion/transport/v2 v2.2.3 h1:XcOE3/x41HOSKbl1BfyY1TF1dERx7lVvlMCbXU7kfvA=
|
||||
github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
||||
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
|
||||
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
||||
github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA=
|
||||
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
|
||||
github.com/pion/webrtc/v3 v3.2.28 h1:ienStxZ6HcjtH2UlmnFpMM0loENiYjaX437uIUpQSKo=
|
||||
github.com/pion/webrtc/v3 v3.2.28/go.mod h1:PNRCEuQlibrmuBhOTnol9j6KkIbUG11aHLEfNpUYey0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.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 v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
|
||||
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
|
||||
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU=
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs=
|
||||
go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
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-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201026091529-146b70c837a4 h1:awiuzyrRjJDb+OXi9ceHO3SDxVoN3JER57mhtqkdQBs=
|
||||
golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6 h1:rbvTkL9AkFts1cgI78+gG6Yu1pwaqX6hjSJAatB78E4=
|
||||
golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda h1:b6F6WIV4xHHD0FA4oIyzU6mHWg2WI2X1RBehwa5QN38=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda/go.mod h1:AHcE/gZH76Bk/ROZhQphlRoWo5xKDEtz3eVEO1LfA8c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
|
||||
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
472
infra/link.pb.go
Normal file
472
infra/link.pb.go
Normal file
@@ -0,0 +1,472 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.2
|
||||
// protoc v4.23.4
|
||||
// source: infra/link.proto
|
||||
|
||||
package infra
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type WatchLocalLinksRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *WatchLocalLinksRequest) Reset() {
|
||||
*x = WatchLocalLinksRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_infra_link_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *WatchLocalLinksRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*WatchLocalLinksRequest) ProtoMessage() {}
|
||||
|
||||
func (x *WatchLocalLinksRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_infra_link_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use WatchLocalLinksRequest.ProtoReflect.Descriptor instead.
|
||||
func (*WatchLocalLinksRequest) Descriptor() ([]byte, []int) {
|
||||
return file_infra_link_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type WatchLocalLinksResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
LocalRegion string `protobuf:"bytes,1,opt,name=local_region,json=localRegion,proto3" json:"local_region,omitempty"`
|
||||
RemoteRegion string `protobuf:"bytes,2,opt,name=remote_region,json=remoteRegion,proto3" json:"remote_region,omitempty"`
|
||||
Rtt int64 `protobuf:"varint,3,opt,name=rtt,proto3" json:"rtt,omitempty"`
|
||||
Jitter int64 `protobuf:"varint,4,opt,name=jitter,proto3" json:"jitter,omitempty"`
|
||||
PacketLoss float64 `protobuf:"fixed64,5,opt,name=packet_loss,json=packetLoss,proto3" json:"packet_loss,omitempty"`
|
||||
Disabled bool `protobuf:"varint,6,opt,name=disabled,proto3" json:"disabled,omitempty"`
|
||||
LastRead *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=last_read,json=lastRead,proto3" json:"last_read,omitempty"`
|
||||
}
|
||||
|
||||
func (x *WatchLocalLinksResponse) Reset() {
|
||||
*x = WatchLocalLinksResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_infra_link_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *WatchLocalLinksResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*WatchLocalLinksResponse) ProtoMessage() {}
|
||||
|
||||
func (x *WatchLocalLinksResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_infra_link_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use WatchLocalLinksResponse.ProtoReflect.Descriptor instead.
|
||||
func (*WatchLocalLinksResponse) Descriptor() ([]byte, []int) {
|
||||
return file_infra_link_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *WatchLocalLinksResponse) GetLocalRegion() string {
|
||||
if x != nil {
|
||||
return x.LocalRegion
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *WatchLocalLinksResponse) GetRemoteRegion() string {
|
||||
if x != nil {
|
||||
return x.RemoteRegion
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *WatchLocalLinksResponse) GetRtt() int64 {
|
||||
if x != nil {
|
||||
return x.Rtt
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *WatchLocalLinksResponse) GetJitter() int64 {
|
||||
if x != nil {
|
||||
return x.Jitter
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *WatchLocalLinksResponse) GetPacketLoss() float64 {
|
||||
if x != nil {
|
||||
return x.PacketLoss
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *WatchLocalLinksResponse) GetDisabled() bool {
|
||||
if x != nil {
|
||||
return x.Disabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *WatchLocalLinksResponse) GetLastRead() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.LastRead
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SimulateLinkStateRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
LocalRegion string `protobuf:"bytes,1,opt,name=local_region,json=localRegion,proto3" json:"local_region,omitempty"`
|
||||
RemoteRegion string `protobuf:"bytes,2,opt,name=remote_region,json=remoteRegion,proto3" json:"remote_region,omitempty"`
|
||||
Rtt *int64 `protobuf:"varint,3,opt,name=rtt,proto3,oneof" json:"rtt,omitempty"`
|
||||
Jitter *int64 `protobuf:"varint,4,opt,name=jitter,proto3,oneof" json:"jitter,omitempty"`
|
||||
PacketLoss *float64 `protobuf:"fixed64,5,opt,name=packet_loss,json=packetLoss,proto3,oneof" json:"packet_loss,omitempty"`
|
||||
Disabled *bool `protobuf:"varint,6,opt,name=disabled,proto3,oneof" json:"disabled,omitempty"`
|
||||
Timeout int64 `protobuf:"varint,7,opt,name=timeout,proto3" json:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
func (x *SimulateLinkStateRequest) Reset() {
|
||||
*x = SimulateLinkStateRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_infra_link_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SimulateLinkStateRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SimulateLinkStateRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SimulateLinkStateRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_infra_link_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SimulateLinkStateRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SimulateLinkStateRequest) Descriptor() ([]byte, []int) {
|
||||
return file_infra_link_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *SimulateLinkStateRequest) GetLocalRegion() string {
|
||||
if x != nil {
|
||||
return x.LocalRegion
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SimulateLinkStateRequest) GetRemoteRegion() string {
|
||||
if x != nil {
|
||||
return x.RemoteRegion
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SimulateLinkStateRequest) GetRtt() int64 {
|
||||
if x != nil && x.Rtt != nil {
|
||||
return *x.Rtt
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SimulateLinkStateRequest) GetJitter() int64 {
|
||||
if x != nil && x.Jitter != nil {
|
||||
return *x.Jitter
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SimulateLinkStateRequest) GetPacketLoss() float64 {
|
||||
if x != nil && x.PacketLoss != nil {
|
||||
return *x.PacketLoss
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SimulateLinkStateRequest) GetDisabled() bool {
|
||||
if x != nil && x.Disabled != nil {
|
||||
return *x.Disabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *SimulateLinkStateRequest) GetTimeout() int64 {
|
||||
if x != nil {
|
||||
return x.Timeout
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type SimulateLinkStateResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *SimulateLinkStateResponse) Reset() {
|
||||
*x = SimulateLinkStateResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_infra_link_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SimulateLinkStateResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SimulateLinkStateResponse) ProtoMessage() {}
|
||||
|
||||
func (x *SimulateLinkStateResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_infra_link_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SimulateLinkStateResponse.ProtoReflect.Descriptor instead.
|
||||
func (*SimulateLinkStateResponse) Descriptor() ([]byte, []int) {
|
||||
return file_infra_link_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
var File_infra_link_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_infra_link_proto_rawDesc = []byte{
|
||||
0x0a, 0x10, 0x69, 0x6e, 0x66, 0x72, 0x61, 0x2f, 0x6c, 0x69, 0x6e, 0x6b, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x12, 0x03, 0x72, 0x70, 0x63, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
||||
0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x18, 0x0a, 0x16, 0x57, 0x61, 0x74, 0x63,
|
||||
0x68, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x4c, 0x69, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x22, 0x81, 0x02, 0x0a, 0x17, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x63, 0x61,
|
||||
0x6c, 0x4c, 0x69, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21,
|
||||
0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x67, 0x69, 0x6f,
|
||||
0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69,
|
||||
0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65,
|
||||
0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x74, 0x74, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x03, 0x52, 0x03, 0x72, 0x74, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6a, 0x69, 0x74, 0x74,
|
||||
0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6a, 0x69, 0x74, 0x74, 0x65, 0x72,
|
||||
0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x6c, 0x6f, 0x73, 0x73, 0x18,
|
||||
0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x4c, 0x6f, 0x73,
|
||||
0x73, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x37, 0x0a,
|
||||
0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x6c, 0x61,
|
||||
0x73, 0x74, 0x52, 0x65, 0x61, 0x64, 0x22, 0xa7, 0x02, 0x0a, 0x18, 0x53, 0x69, 0x6d, 0x75, 0x6c,
|
||||
0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x67,
|
||||
0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
|
||||
0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65,
|
||||
0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72,
|
||||
0x65, 0x6d, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x03, 0x72,
|
||||
0x74, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x03, 0x72, 0x74, 0x74, 0x88,
|
||||
0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x6a, 0x69, 0x74, 0x74, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x03, 0x48, 0x01, 0x52, 0x06, 0x6a, 0x69, 0x74, 0x74, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12,
|
||||
0x24, 0x0a, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x6c, 0x6f, 0x73, 0x73, 0x18, 0x05,
|
||||
0x20, 0x01, 0x28, 0x01, 0x48, 0x02, 0x52, 0x0a, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x4c, 0x6f,
|
||||
0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
|
||||
0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x48, 0x03, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62,
|
||||
0x6c, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75,
|
||||
0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74,
|
||||
0x42, 0x06, 0x0a, 0x04, 0x5f, 0x72, 0x74, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6a, 0x69, 0x74,
|
||||
0x74, 0x65, 0x72, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x6c,
|
||||
0x6f, 0x73, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64,
|
||||
0x22, 0x1b, 0x0a, 0x19, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b,
|
||||
0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xaa, 0x01,
|
||||
0x0a, 0x04, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x4e, 0x0a, 0x0f, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4c,
|
||||
0x6f, 0x63, 0x61, 0x6c, 0x4c, 0x69, 0x6e, 0x6b, 0x73, 0x12, 0x1b, 0x2e, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x57, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x4c, 0x69, 0x6e, 0x6b, 0x73, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x74,
|
||||
0x63, 0x68, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x4c, 0x69, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x52, 0x0a, 0x11, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61,
|
||||
0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1d, 0x2e, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74,
|
||||
0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61,
|
||||
0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x23, 0x5a, 0x21, 0x67, 0x69,
|
||||
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x76, 0x65, 0x6b, 0x69, 0x74,
|
||||
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x69, 0x6e, 0x66, 0x72, 0x61, 0x62,
|
||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_infra_link_proto_rawDescOnce sync.Once
|
||||
file_infra_link_proto_rawDescData = file_infra_link_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_infra_link_proto_rawDescGZIP() []byte {
|
||||
file_infra_link_proto_rawDescOnce.Do(func() {
|
||||
file_infra_link_proto_rawDescData = protoimpl.X.CompressGZIP(file_infra_link_proto_rawDescData)
|
||||
})
|
||||
return file_infra_link_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_infra_link_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_infra_link_proto_goTypes = []any{
|
||||
(*WatchLocalLinksRequest)(nil), // 0: rpc.WatchLocalLinksRequest
|
||||
(*WatchLocalLinksResponse)(nil), // 1: rpc.WatchLocalLinksResponse
|
||||
(*SimulateLinkStateRequest)(nil), // 2: rpc.SimulateLinkStateRequest
|
||||
(*SimulateLinkStateResponse)(nil), // 3: rpc.SimulateLinkStateResponse
|
||||
(*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp
|
||||
}
|
||||
var file_infra_link_proto_depIdxs = []int32{
|
||||
4, // 0: rpc.WatchLocalLinksResponse.last_read:type_name -> google.protobuf.Timestamp
|
||||
0, // 1: rpc.Link.WatchLocalLinks:input_type -> rpc.WatchLocalLinksRequest
|
||||
2, // 2: rpc.Link.SimulateLinkState:input_type -> rpc.SimulateLinkStateRequest
|
||||
1, // 3: rpc.Link.WatchLocalLinks:output_type -> rpc.WatchLocalLinksResponse
|
||||
3, // 4: rpc.Link.SimulateLinkState:output_type -> rpc.SimulateLinkStateResponse
|
||||
3, // [3:5] is the sub-list for method output_type
|
||||
1, // [1:3] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_infra_link_proto_init() }
|
||||
func file_infra_link_proto_init() {
|
||||
if File_infra_link_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_infra_link_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*WatchLocalLinksRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_infra_link_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*WatchLocalLinksResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_infra_link_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*SimulateLinkStateRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_infra_link_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*SimulateLinkStateResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
file_infra_link_proto_msgTypes[2].OneofWrappers = []any{}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_infra_link_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_infra_link_proto_goTypes,
|
||||
DependencyIndexes: file_infra_link_proto_depIdxs,
|
||||
MessageInfos: file_infra_link_proto_msgTypes,
|
||||
}.Build()
|
||||
File_infra_link_proto = out.File
|
||||
file_infra_link_proto_rawDesc = nil
|
||||
file_infra_link_proto_goTypes = nil
|
||||
file_infra_link_proto_depIdxs = nil
|
||||
}
|
||||
190
infra/link_grpc.pb.go
Normal file
190
infra/link_grpc.pb.go
Normal file
@@ -0,0 +1,190 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.4.0
|
||||
// - protoc v4.23.4
|
||||
// source: infra/link.proto
|
||||
|
||||
package infra
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.62.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion8
|
||||
|
||||
const (
|
||||
Link_WatchLocalLinks_FullMethodName = "/rpc.Link/WatchLocalLinks"
|
||||
Link_SimulateLinkState_FullMethodName = "/rpc.Link/SimulateLinkState"
|
||||
)
|
||||
|
||||
// LinkClient is the client API for Link service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type LinkClient interface {
|
||||
WatchLocalLinks(ctx context.Context, in *WatchLocalLinksRequest, opts ...grpc.CallOption) (Link_WatchLocalLinksClient, error)
|
||||
SimulateLinkState(ctx context.Context, in *SimulateLinkStateRequest, opts ...grpc.CallOption) (*SimulateLinkStateResponse, error)
|
||||
}
|
||||
|
||||
type linkClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewLinkClient(cc grpc.ClientConnInterface) LinkClient {
|
||||
return &linkClient{cc}
|
||||
}
|
||||
|
||||
func (c *linkClient) WatchLocalLinks(ctx context.Context, in *WatchLocalLinksRequest, opts ...grpc.CallOption) (Link_WatchLocalLinksClient, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &Link_ServiceDesc.Streams[0], Link_WatchLocalLinks_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &linkWatchLocalLinksClient{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type Link_WatchLocalLinksClient interface {
|
||||
Recv() (*WatchLocalLinksResponse, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type linkWatchLocalLinksClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *linkWatchLocalLinksClient) Recv() (*WatchLocalLinksResponse, error) {
|
||||
m := new(WatchLocalLinksResponse)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *linkClient) SimulateLinkState(ctx context.Context, in *SimulateLinkStateRequest, opts ...grpc.CallOption) (*SimulateLinkStateResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(SimulateLinkStateResponse)
|
||||
err := c.cc.Invoke(ctx, Link_SimulateLinkState_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// LinkServer is the server API for Link service.
|
||||
// All implementations must embed UnimplementedLinkServer
|
||||
// for forward compatibility
|
||||
type LinkServer interface {
|
||||
WatchLocalLinks(*WatchLocalLinksRequest, Link_WatchLocalLinksServer) error
|
||||
SimulateLinkState(context.Context, *SimulateLinkStateRequest) (*SimulateLinkStateResponse, error)
|
||||
mustEmbedUnimplementedLinkServer()
|
||||
}
|
||||
|
||||
// UnimplementedLinkServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedLinkServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedLinkServer) WatchLocalLinks(*WatchLocalLinksRequest, Link_WatchLocalLinksServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method WatchLocalLinks not implemented")
|
||||
}
|
||||
func (UnimplementedLinkServer) SimulateLinkState(context.Context, *SimulateLinkStateRequest) (*SimulateLinkStateResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SimulateLinkState not implemented")
|
||||
}
|
||||
func (UnimplementedLinkServer) mustEmbedUnimplementedLinkServer() {}
|
||||
|
||||
// UnsafeLinkServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to LinkServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeLinkServer interface {
|
||||
mustEmbedUnimplementedLinkServer()
|
||||
}
|
||||
|
||||
func RegisterLinkServer(s grpc.ServiceRegistrar, srv LinkServer) {
|
||||
s.RegisterService(&Link_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _Link_WatchLocalLinks_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(WatchLocalLinksRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(LinkServer).WatchLocalLinks(m, &linkWatchLocalLinksServer{ServerStream: stream})
|
||||
}
|
||||
|
||||
type Link_WatchLocalLinksServer interface {
|
||||
Send(*WatchLocalLinksResponse) error
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type linkWatchLocalLinksServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *linkWatchLocalLinksServer) Send(m *WatchLocalLinksResponse) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func _Link_SimulateLinkState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SimulateLinkStateRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LinkServer).SimulateLinkState(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Link_SimulateLinkState_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LinkServer).SimulateLinkState(ctx, req.(*SimulateLinkStateRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// Link_ServiceDesc is the grpc.ServiceDesc for Link service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var Link_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "rpc.Link",
|
||||
HandlerType: (*LinkServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "SimulateLinkState",
|
||||
Handler: _Link_SimulateLinkState_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "WatchLocalLinks",
|
||||
Handler: _Link_WatchLocalLinks_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "infra/link.proto",
|
||||
}
|
||||
45
ingress/errors.go
Normal file
45
ingress/errors.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/livekit/psrpc"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIngressOutOfDate = psrpc.NewErrorf(psrpc.FailedPrecondition, "trying to ovewrite an ingress with an older version")
|
||||
ErrIngressTimedOut = psrpc.NewErrorf(psrpc.DeadlineExceeded, "ingress timed out")
|
||||
ErrNoResponse = psrpc.NewErrorf(psrpc.Unavailable, "no response from ingress service")
|
||||
ErrInvalidOutputDimensions = NewInvalidVideoParamsError("invalid output media dimensions")
|
||||
ErrInvalidIngressType = psrpc.NewErrorf(psrpc.InvalidArgument, "invalid ingress type")
|
||||
)
|
||||
|
||||
func ErrInvalidIngress(s string) psrpc.Error {
|
||||
return psrpc.NewErrorf(psrpc.InvalidArgument, "invalid ingress: %s", s)
|
||||
}
|
||||
|
||||
func NewInvalidVideoParamsError(s string) error {
|
||||
return psrpc.NewError(psrpc.InvalidArgument, fmt.Errorf("invalid video parameters: %s", s))
|
||||
}
|
||||
|
||||
func NewInvalidAudioParamsError(s string) error {
|
||||
return psrpc.NewError(psrpc.InvalidArgument, fmt.Errorf("invalid audio parameters: %s", s))
|
||||
}
|
||||
|
||||
func NewInvalidTranscodingBypassError(s string) error {
|
||||
return psrpc.NewError(psrpc.InvalidArgument, fmt.Errorf("invalid transcoding bypass configuration: %s", s))
|
||||
}
|
||||
43
ingress/token.go
Normal file
43
ingress/token.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
func BuildIngressToken(apiKey, secret, roomName, participantIdentity, participantName, participantMetadata string) (string, error) {
|
||||
f := false
|
||||
t := true
|
||||
grant := &auth.VideoGrant{
|
||||
RoomJoin: true,
|
||||
Room: roomName,
|
||||
CanSubscribe: &f,
|
||||
CanPublish: &t,
|
||||
}
|
||||
|
||||
at := auth.NewAccessToken(apiKey, secret).
|
||||
AddGrant(grant).
|
||||
SetIdentity(participantIdentity).
|
||||
SetName(participantName).
|
||||
SetKind(livekit.ParticipantInfo_INGRESS).
|
||||
SetValidFor(24 * time.Hour).
|
||||
SetMetadata(participantMetadata)
|
||||
|
||||
return at.ToJWT()
|
||||
}
|
||||
283
ingress/validation.go
Normal file
283
ingress/validation.go
Normal file
@@ -0,0 +1,283 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
// This validates that ingress options have no consistency issues and provide enough parameters
|
||||
// to be usable by the ingress service. Options that pass this test may still need some fields to be poulated with default values
|
||||
// before being used in a media pipeline.
|
||||
|
||||
func Validate(info *livekit.IngressInfo) error {
|
||||
if info == nil {
|
||||
return ErrInvalidIngress("missing IngressInfo")
|
||||
}
|
||||
|
||||
// For now, require a room to be set. We should eventually allow changing the room on an active ingress
|
||||
if info.RoomName == "" {
|
||||
return ErrInvalidIngress("no room name")
|
||||
}
|
||||
|
||||
return ValidateForSerialization(info)
|
||||
}
|
||||
|
||||
// Sparse info with not all required fields populated are acceptable for serialization, provided values are consistent
|
||||
func ValidateForSerialization(info *livekit.IngressInfo) error {
|
||||
if info == nil {
|
||||
return ErrInvalidIngress("missing IngressInfo")
|
||||
}
|
||||
|
||||
if info.InputType != livekit.IngressInput_RTMP_INPUT && info.InputType != livekit.IngressInput_WHIP_INPUT && info.InputType != livekit.IngressInput_URL_INPUT {
|
||||
return ErrInvalidIngress("unsupported input type")
|
||||
}
|
||||
|
||||
// Validate source
|
||||
switch info.InputType {
|
||||
case livekit.IngressInput_RTMP_INPUT,
|
||||
livekit.IngressInput_WHIP_INPUT:
|
||||
if info.StreamKey == "" {
|
||||
return ErrInvalidIngress("no stream key")
|
||||
}
|
||||
case livekit.IngressInput_URL_INPUT:
|
||||
if info.Url == "" {
|
||||
return ErrInvalidIngress("no source URL")
|
||||
}
|
||||
}
|
||||
|
||||
if info.ParticipantIdentity == "" {
|
||||
return ErrInvalidIngress("no participant identity")
|
||||
}
|
||||
|
||||
err := ValidateBypassTranscoding(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ValidateEnableTranscoding(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ValidateVideoOptionsConsistency(info.Video)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ValidateAudioOptionsConsistency(info.Audio)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func ValidateBypassTranscoding(info *livekit.IngressInfo) error {
|
||||
if !info.BypassTranscoding {
|
||||
return nil
|
||||
}
|
||||
|
||||
if info.InputType != livekit.IngressInput_WHIP_INPUT {
|
||||
return NewInvalidTranscodingBypassError("bypassing transcoding impossible with selected input type")
|
||||
}
|
||||
|
||||
videoOptions := info.Video
|
||||
if videoOptions != nil && videoOptions.EncodingOptions != nil {
|
||||
return NewInvalidTranscodingBypassError("video encoding options must be empty if transcoding bypass is selected")
|
||||
}
|
||||
|
||||
audioOptions := info.Audio
|
||||
if audioOptions != nil && audioOptions.EncodingOptions != nil {
|
||||
return NewInvalidTranscodingBypassError("audio encoding options must be empty if transcoding bypass is selected")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateEnableTranscoding(info *livekit.IngressInfo) error {
|
||||
var enableTranscoding bool
|
||||
if info.InputType == livekit.IngressInput_WHIP_INPUT {
|
||||
enableTranscoding = false
|
||||
if info.EnableTranscoding != nil {
|
||||
enableTranscoding = *info.EnableTranscoding
|
||||
}
|
||||
} else {
|
||||
enableTranscoding = true
|
||||
if info.EnableTranscoding != nil && *info.EnableTranscoding == false {
|
||||
return NewInvalidTranscodingBypassError("bypassing transcoding impossible with selected input type")
|
||||
}
|
||||
}
|
||||
|
||||
if !enableTranscoding {
|
||||
videoOptions := info.Video
|
||||
if videoOptions != nil && videoOptions.EncodingOptions != nil {
|
||||
return NewInvalidTranscodingBypassError("video encoding options must be empty if transcoding is disabled")
|
||||
}
|
||||
|
||||
audioOptions := info.Audio
|
||||
if audioOptions != nil && audioOptions.EncodingOptions != nil {
|
||||
return NewInvalidTranscodingBypassError("audio encoding options must be empty if transcoding is disabled")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateVideoOptionsConsistency(options *livekit.IngressVideoOptions) error {
|
||||
if options == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch options.Source {
|
||||
case livekit.TrackSource_UNKNOWN,
|
||||
livekit.TrackSource_CAMERA,
|
||||
livekit.TrackSource_SCREEN_SHARE:
|
||||
// continue
|
||||
default:
|
||||
return NewInvalidVideoParamsError("invalid track source")
|
||||
}
|
||||
|
||||
switch o := options.EncodingOptions.(type) {
|
||||
case nil:
|
||||
// Default, continue
|
||||
case *livekit.IngressVideoOptions_Preset:
|
||||
_, ok := livekit.IngressVideoEncodingPreset_name[int32(o.Preset)]
|
||||
if !ok {
|
||||
return NewInvalidVideoParamsError("invalid preset")
|
||||
}
|
||||
|
||||
case *livekit.IngressVideoOptions_Options:
|
||||
err := ValidateVideoEncodingOptionsConsistency(o.Options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateVideoEncodingOptionsConsistency(options *livekit.IngressVideoEncodingOptions) error {
|
||||
if options == nil {
|
||||
return NewInvalidVideoParamsError("empty options")
|
||||
}
|
||||
|
||||
if options.FrameRate < 0 {
|
||||
return NewInvalidVideoParamsError("invalid framerate")
|
||||
}
|
||||
|
||||
switch options.VideoCodec {
|
||||
case livekit.VideoCodec_DEFAULT_VC,
|
||||
livekit.VideoCodec_H264_BASELINE,
|
||||
livekit.VideoCodec_VP8:
|
||||
// continue
|
||||
default:
|
||||
return NewInvalidVideoParamsError("video codec unsupported")
|
||||
}
|
||||
|
||||
layersByQuality := make(map[livekit.VideoQuality]*livekit.VideoLayer)
|
||||
|
||||
for _, layer := range options.Layers {
|
||||
if layer.Height == 0 || layer.Width == 0 {
|
||||
return ErrInvalidOutputDimensions
|
||||
}
|
||||
|
||||
if layer.Width%2 == 1 {
|
||||
return ErrInvalidIngress("layer width must be even")
|
||||
}
|
||||
|
||||
if layer.Height%2 == 1 {
|
||||
return ErrInvalidIngress("layer height must be even")
|
||||
}
|
||||
|
||||
if _, ok := layersByQuality[layer.Quality]; ok {
|
||||
return NewInvalidVideoParamsError("more than one layer with the same quality level")
|
||||
}
|
||||
layersByQuality[layer.Quality] = layer
|
||||
}
|
||||
|
||||
var oldW, oldH uint32
|
||||
for q := livekit.VideoQuality_LOW; q <= livekit.VideoQuality_HIGH; q++ {
|
||||
layer, ok := layersByQuality[q]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if layer.Height < oldH {
|
||||
return NewInvalidVideoParamsError("video layers do not have increasing height with increasing quality")
|
||||
}
|
||||
if layer.Width < oldW {
|
||||
return NewInvalidVideoParamsError("video layers do not have increasing width with increasing quality")
|
||||
}
|
||||
oldW = layer.Width
|
||||
oldH = layer.Height
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateAudioOptionsConsistency(options *livekit.IngressAudioOptions) error {
|
||||
if options == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch options.Source {
|
||||
case livekit.TrackSource_UNKNOWN,
|
||||
livekit.TrackSource_MICROPHONE,
|
||||
livekit.TrackSource_SCREEN_SHARE_AUDIO:
|
||||
// continue
|
||||
default:
|
||||
return NewInvalidAudioParamsError("invalid track source")
|
||||
}
|
||||
|
||||
switch o := options.EncodingOptions.(type) {
|
||||
case nil:
|
||||
// Default, continue
|
||||
case *livekit.IngressAudioOptions_Preset:
|
||||
_, ok := livekit.IngressAudioEncodingPreset_name[int32(o.Preset)]
|
||||
if !ok {
|
||||
return NewInvalidAudioParamsError("invalid preset")
|
||||
}
|
||||
|
||||
case *livekit.IngressAudioOptions_Options:
|
||||
err := ValidateAudioEncodingOptionsConsistency(o.Options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateAudioEncodingOptionsConsistency(options *livekit.IngressAudioEncodingOptions) error {
|
||||
if options == nil {
|
||||
return NewInvalidAudioParamsError("empty options")
|
||||
}
|
||||
|
||||
switch options.AudioCodec {
|
||||
case livekit.AudioCodec_DEFAULT_AC,
|
||||
livekit.AudioCodec_OPUS:
|
||||
// continue
|
||||
default:
|
||||
return NewInvalidAudioParamsError("audio codec unsupported")
|
||||
}
|
||||
|
||||
if options.Channels > 2 {
|
||||
return NewInvalidAudioParamsError("invalid audio channel count")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
312
ingress/validation_test.go
Normal file
312
ingress/validation_test.go
Normal file
@@ -0,0 +1,312 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
info := &livekit.IngressInfo{}
|
||||
|
||||
err := Validate(info)
|
||||
require.Error(t, err)
|
||||
|
||||
info.StreamKey = "stream_key"
|
||||
err = Validate(info)
|
||||
require.Error(t, err)
|
||||
|
||||
info.RoomName = "room_name"
|
||||
err = Validate(info)
|
||||
require.Error(t, err)
|
||||
|
||||
info.ParticipantIdentity = "participant_identity"
|
||||
err = Validate(info)
|
||||
require.NoError(t, err)
|
||||
|
||||
// make sure video parameters are validated. Full validation logic tested in the next test
|
||||
info.Video = &livekit.IngressVideoOptions{}
|
||||
err = Validate(info)
|
||||
require.NoError(t, err)
|
||||
|
||||
info.Video.Source = livekit.TrackSource_MICROPHONE
|
||||
err = Validate(info)
|
||||
require.Error(t, err)
|
||||
|
||||
info.Video.Source = livekit.TrackSource_CAMERA
|
||||
|
||||
// make sure audio parameters are validated. Full validation logic tested in the next test
|
||||
info.Audio = &livekit.IngressAudioOptions{}
|
||||
err = Validate(info)
|
||||
require.NoError(t, err)
|
||||
|
||||
info.Audio.Source = livekit.TrackSource_CAMERA
|
||||
err = Validate(info)
|
||||
require.Error(t, err)
|
||||
|
||||
info.Audio.Source = livekit.TrackSource_SCREEN_SHARE_AUDIO
|
||||
err = Validate(info)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestValidateBypassTranscoding(t *testing.T) {
|
||||
info := &livekit.IngressInfo{}
|
||||
|
||||
err := ValidateBypassTranscoding(info)
|
||||
require.NoError(t, err)
|
||||
|
||||
info.BypassTranscoding = true
|
||||
err = ValidateBypassTranscoding(info)
|
||||
require.Error(t, err)
|
||||
|
||||
info.InputType = livekit.IngressInput_WHIP_INPUT
|
||||
err = ValidateBypassTranscoding(info)
|
||||
require.NoError(t, err)
|
||||
|
||||
info.Video = &livekit.IngressVideoOptions{}
|
||||
err = ValidateBypassTranscoding(info)
|
||||
require.NoError(t, err)
|
||||
|
||||
info.Video.EncodingOptions = &livekit.IngressVideoOptions_Preset{}
|
||||
err = ValidateBypassTranscoding(info)
|
||||
require.Error(t, err)
|
||||
|
||||
info.Video = nil
|
||||
|
||||
info.Audio = &livekit.IngressAudioOptions{}
|
||||
err = ValidateBypassTranscoding(info)
|
||||
require.NoError(t, err)
|
||||
|
||||
info.Audio.EncodingOptions = &livekit.IngressAudioOptions_Options{
|
||||
Options: &livekit.IngressAudioEncodingOptions{},
|
||||
}
|
||||
err = ValidateBypassTranscoding(info)
|
||||
require.Error(t, err)
|
||||
|
||||
}
|
||||
|
||||
func TestValidateEnableTranscoding(t *testing.T) {
|
||||
info := &livekit.IngressInfo{}
|
||||
T := true
|
||||
F := false
|
||||
|
||||
err := ValidateEnableTranscoding(info)
|
||||
require.NoError(t, err)
|
||||
|
||||
info.InputType = livekit.IngressInput_WHIP_INPUT
|
||||
err = ValidateEnableTranscoding(info)
|
||||
require.NoError(t, err)
|
||||
|
||||
info.Audio = &livekit.IngressAudioOptions{}
|
||||
info.Audio.EncodingOptions = &livekit.IngressAudioOptions_Options{}
|
||||
err = ValidateEnableTranscoding(info)
|
||||
require.Error(t, err)
|
||||
|
||||
info.Audio.EncodingOptions = nil
|
||||
|
||||
info.EnableTranscoding = &T
|
||||
err = ValidateEnableTranscoding(info)
|
||||
require.NoError(t, err)
|
||||
|
||||
info.EnableTranscoding = &F
|
||||
err = ValidateEnableTranscoding(info)
|
||||
require.NoError(t, err)
|
||||
|
||||
info.Video = &livekit.IngressVideoOptions{}
|
||||
info.Video.EncodingOptions = &livekit.IngressVideoOptions_Preset{}
|
||||
err = ValidateEnableTranscoding(info)
|
||||
require.Error(t, err)
|
||||
|
||||
info.Video.EncodingOptions = nil
|
||||
|
||||
info.InputType = livekit.IngressInput_RTMP_INPUT
|
||||
err = ValidateEnableTranscoding(info)
|
||||
require.Error(t, err)
|
||||
|
||||
info.EnableTranscoding = &T
|
||||
err = ValidateEnableTranscoding(info)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestValidateVideoOptionsConsistency(t *testing.T) {
|
||||
video := &livekit.IngressVideoOptions{}
|
||||
err := ValidateVideoOptionsConsistency(video)
|
||||
require.NoError(t, err)
|
||||
|
||||
video.Source = livekit.TrackSource_MICROPHONE
|
||||
err = ValidateVideoOptionsConsistency(video)
|
||||
require.Error(t, err)
|
||||
|
||||
video.Source = livekit.TrackSource_CAMERA
|
||||
video.EncodingOptions = &livekit.IngressVideoOptions_Preset{
|
||||
Preset: livekit.IngressVideoEncodingPreset(42),
|
||||
}
|
||||
err = ValidateVideoOptionsConsistency(video)
|
||||
require.Error(t, err)
|
||||
|
||||
video.EncodingOptions = &livekit.IngressVideoOptions_Preset{
|
||||
Preset: livekit.IngressVideoEncodingPreset_H264_1080P_30FPS_1_LAYER,
|
||||
}
|
||||
err = ValidateVideoOptionsConsistency(video)
|
||||
require.NoError(t, err)
|
||||
|
||||
video.EncodingOptions = &livekit.IngressVideoOptions_Options{
|
||||
Options: &livekit.IngressVideoEncodingOptions{
|
||||
VideoCodec: livekit.VideoCodec_H264_HIGH,
|
||||
},
|
||||
}
|
||||
err = ValidateVideoOptionsConsistency(video)
|
||||
require.Error(t, err)
|
||||
|
||||
video.EncodingOptions = &livekit.IngressVideoOptions_Options{
|
||||
Options: &livekit.IngressVideoEncodingOptions{
|
||||
VideoCodec: livekit.VideoCodec_DEFAULT_VC,
|
||||
},
|
||||
}
|
||||
err = ValidateVideoOptionsConsistency(video)
|
||||
require.NoError(t, err)
|
||||
|
||||
video.EncodingOptions.(*livekit.IngressVideoOptions_Options).Options.Layers = []*livekit.VideoLayer{
|
||||
&livekit.VideoLayer{},
|
||||
}
|
||||
err = ValidateVideoOptionsConsistency(video)
|
||||
require.Error(t, err)
|
||||
|
||||
video.EncodingOptions.(*livekit.IngressVideoOptions_Options).Options.Layers = []*livekit.VideoLayer{
|
||||
&livekit.VideoLayer{
|
||||
Width: 640,
|
||||
Height: 480,
|
||||
},
|
||||
}
|
||||
err = ValidateVideoOptionsConsistency(video)
|
||||
require.NoError(t, err)
|
||||
|
||||
video.EncodingOptions.(*livekit.IngressVideoOptions_Options).Options.Layers = []*livekit.VideoLayer{
|
||||
&livekit.VideoLayer{
|
||||
Width: 641,
|
||||
Height: 480,
|
||||
},
|
||||
}
|
||||
err = ValidateVideoOptionsConsistency(video)
|
||||
require.Error(t, err)
|
||||
|
||||
video.EncodingOptions.(*livekit.IngressVideoOptions_Options).Options.Layers = []*livekit.VideoLayer{
|
||||
&livekit.VideoLayer{
|
||||
Width: 640,
|
||||
Height: 480,
|
||||
Quality: livekit.VideoQuality_HIGH,
|
||||
},
|
||||
&livekit.VideoLayer{
|
||||
Width: 640,
|
||||
Height: 480,
|
||||
Quality: livekit.VideoQuality_LOW,
|
||||
},
|
||||
}
|
||||
err = ValidateVideoOptionsConsistency(video)
|
||||
require.NoError(t, err)
|
||||
|
||||
video.EncodingOptions.(*livekit.IngressVideoOptions_Options).Options.Layers = []*livekit.VideoLayer{
|
||||
&livekit.VideoLayer{
|
||||
Width: 640,
|
||||
Height: 480,
|
||||
Quality: livekit.VideoQuality_HIGH,
|
||||
},
|
||||
&livekit.VideoLayer{
|
||||
Width: 1280,
|
||||
Height: 720,
|
||||
Quality: livekit.VideoQuality_HIGH,
|
||||
},
|
||||
}
|
||||
err = ValidateVideoOptionsConsistency(video)
|
||||
require.Error(t, err)
|
||||
|
||||
video.EncodingOptions.(*livekit.IngressVideoOptions_Options).Options.Layers = []*livekit.VideoLayer{
|
||||
&livekit.VideoLayer{
|
||||
Width: 640,
|
||||
Height: 480,
|
||||
Quality: livekit.VideoQuality_HIGH,
|
||||
},
|
||||
&livekit.VideoLayer{
|
||||
Width: 1280,
|
||||
Height: 720,
|
||||
Quality: livekit.VideoQuality_LOW,
|
||||
},
|
||||
}
|
||||
err = ValidateVideoOptionsConsistency(video)
|
||||
require.Error(t, err)
|
||||
|
||||
video.EncodingOptions.(*livekit.IngressVideoOptions_Options).Options.Layers = []*livekit.VideoLayer{
|
||||
&livekit.VideoLayer{
|
||||
Width: 640,
|
||||
Height: 480,
|
||||
Quality: livekit.VideoQuality_LOW,
|
||||
},
|
||||
&livekit.VideoLayer{
|
||||
Width: 1280,
|
||||
Height: 720,
|
||||
Quality: livekit.VideoQuality_HIGH,
|
||||
},
|
||||
}
|
||||
|
||||
err = ValidateVideoOptionsConsistency(video)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestValidateAudioOptionsConsistency(t *testing.T) {
|
||||
audio := &livekit.IngressAudioOptions{}
|
||||
err := ValidateAudioOptionsConsistency(audio)
|
||||
require.NoError(t, err)
|
||||
|
||||
audio.Source = livekit.TrackSource_CAMERA
|
||||
err = ValidateAudioOptionsConsistency(audio)
|
||||
require.Error(t, err)
|
||||
|
||||
audio.Source = livekit.TrackSource_SCREEN_SHARE_AUDIO
|
||||
audio.EncodingOptions = &livekit.IngressAudioOptions_Preset{
|
||||
Preset: livekit.IngressAudioEncodingPreset(42),
|
||||
}
|
||||
err = ValidateAudioOptionsConsistency(audio)
|
||||
require.Error(t, err)
|
||||
|
||||
audio.EncodingOptions = &livekit.IngressAudioOptions_Preset{
|
||||
Preset: livekit.IngressAudioEncodingPreset_OPUS_MONO_64KBS,
|
||||
}
|
||||
err = ValidateAudioOptionsConsistency(audio)
|
||||
require.NoError(t, err)
|
||||
|
||||
audio.EncodingOptions = &livekit.IngressAudioOptions_Options{
|
||||
Options: &livekit.IngressAudioEncodingOptions{
|
||||
AudioCodec: livekit.AudioCodec_AAC,
|
||||
},
|
||||
}
|
||||
err = ValidateAudioOptionsConsistency(audio)
|
||||
require.Error(t, err)
|
||||
|
||||
audio.EncodingOptions = &livekit.IngressAudioOptions_Options{
|
||||
Options: &livekit.IngressAudioEncodingOptions{
|
||||
AudioCodec: livekit.AudioCodec_OPUS,
|
||||
Channels: 3,
|
||||
},
|
||||
}
|
||||
err = ValidateAudioOptionsConsistency(audio)
|
||||
require.Error(t, err)
|
||||
|
||||
audio.EncodingOptions.(*livekit.IngressAudioOptions_Options).Options.Channels = 2
|
||||
err = ValidateAudioOptionsConsistency(audio)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package livekit;
|
||||
option go_package = "github.com/livekit/livekit-server/proto/livekit";
|
||||
|
||||
// internal protos, not exposed to clients
|
||||
import "rtc.proto";
|
||||
import "room.proto";
|
||||
|
||||
message Node {
|
||||
string id = 1;
|
||||
string ip = 2;
|
||||
uint32 num_cpus = 3;
|
||||
NodeStats stats = 4;
|
||||
}
|
||||
|
||||
message NodeStats {
|
||||
// when server was started
|
||||
int64 started_at = 1;
|
||||
// when server last reported its status
|
||||
int64 updated_at = 2;
|
||||
uint32 num_rooms = 3;
|
||||
uint32 num_clients = 4;
|
||||
uint32 num_tracks_in = 5;
|
||||
uint32 num_tracks_out = 6;
|
||||
}
|
||||
|
||||
// message to RTC nodes
|
||||
message RTCNodeMessage {
|
||||
string participant_key = 1;
|
||||
oneof message {
|
||||
StartSession start_session = 2;
|
||||
SignalRequest request = 3;
|
||||
// internal messages
|
||||
RoomParticipantIdentity remove_participant = 4;
|
||||
MuteRoomTrackRequest mute_track = 5;
|
||||
}
|
||||
}
|
||||
|
||||
// message to Signal nodes
|
||||
message SignalNodeMessage {
|
||||
string connection_id = 1;
|
||||
oneof message {
|
||||
SignalResponse response = 2;
|
||||
EndSession end_session = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message StartSession {
|
||||
string room_name = 1;
|
||||
string identity = 2;
|
||||
string connection_id = 3;
|
||||
// if a client is reconnecting (i.e. resume instead of restart)
|
||||
bool reconnect = 4;
|
||||
// metadata to pass to participant
|
||||
string metadata = 5;
|
||||
}
|
||||
|
||||
message EndSession {
|
||||
}
|
||||
|
||||
message RemoveParticipant {
|
||||
string participant_id = 1;
|
||||
}
|
||||
1
livekit/README.md
Normal file
1
livekit/README.md
Normal file
@@ -0,0 +1 @@
|
||||
Generated protobuf stubs
|
||||
19
livekit/attrs.go
Normal file
19
livekit/attrs.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package livekit
|
||||
|
||||
// Names of participant attributes for SIP.
|
||||
const (
|
||||
// AttrSIPPrefix is shared for all SIP attributes.
|
||||
AttrSIPPrefix = "sip."
|
||||
// AttrSIPCallID attribute contains LiveKit SIP call ID.
|
||||
AttrSIPCallID = AttrSIPPrefix + "callID"
|
||||
// AttrSIPTrunkID attribute contains LiveKit SIP Trunk ID used for the call.
|
||||
AttrSIPTrunkID = AttrSIPPrefix + "trunkID"
|
||||
// AttrSIPDispatchRuleID attribute contains LiveKit SIP DispatchRule ID used for the inbound call.
|
||||
AttrSIPDispatchRuleID = AttrSIPPrefix + "ruleID"
|
||||
// AttrSIPTrunkNumber attribute contains number associate with LiveKit SIP Trunk.
|
||||
// This attribute will be omitted if HidePhoneNumber is set.
|
||||
AttrSIPTrunkNumber = AttrSIPPrefix + "trunkPhoneNumber"
|
||||
// AttrSIPPhoneNumber attribute contains number external to LiveKit SIP (caller for inbound and called number for outbound).
|
||||
// This attribute will be omitted if HidePhoneNumber is set.
|
||||
AttrSIPPhoneNumber = AttrSIPPrefix + "phoneNumber"
|
||||
)
|
||||
2430
livekit/livekit_agent.pb.go
Normal file
2430
livekit/livekit_agent.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
200
livekit/livekit_agent_grpc.pb.go
Normal file
200
livekit/livekit_agent_grpc.pb.go
Normal file
@@ -0,0 +1,200 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.4.0
|
||||
// - protoc v4.23.4
|
||||
// source: livekit_agent.proto
|
||||
|
||||
package livekit
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.62.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion8
|
||||
|
||||
const (
|
||||
AgentService_CreateAgentJobDefinition_FullMethodName = "/livekit.AgentService/CreateAgentJobDefinition"
|
||||
AgentService_DeleteAgentJobDefinition_FullMethodName = "/livekit.AgentService/DeleteAgentJobDefinition"
|
||||
AgentService_ListAgentJobDefinitions_FullMethodName = "/livekit.AgentService/ListAgentJobDefinitions"
|
||||
)
|
||||
|
||||
// AgentServiceClient is the client API for AgentService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type AgentServiceClient interface {
|
||||
CreateAgentJobDefinition(ctx context.Context, in *CreateAgentJobDefinitionRequest, opts ...grpc.CallOption) (*JobDefinition, error)
|
||||
DeleteAgentJobDefinition(ctx context.Context, in *DeleteAgentJobDefinitionRequest, opts ...grpc.CallOption) (*JobDefinition, error)
|
||||
ListAgentJobDefinitions(ctx context.Context, in *ListAgentJobDefinitionsRequesst, opts ...grpc.CallOption) (*ListAgentJobDefinitionsResponse, error)
|
||||
}
|
||||
|
||||
type agentServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewAgentServiceClient(cc grpc.ClientConnInterface) AgentServiceClient {
|
||||
return &agentServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *agentServiceClient) CreateAgentJobDefinition(ctx context.Context, in *CreateAgentJobDefinitionRequest, opts ...grpc.CallOption) (*JobDefinition, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(JobDefinition)
|
||||
err := c.cc.Invoke(ctx, AgentService_CreateAgentJobDefinition_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *agentServiceClient) DeleteAgentJobDefinition(ctx context.Context, in *DeleteAgentJobDefinitionRequest, opts ...grpc.CallOption) (*JobDefinition, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(JobDefinition)
|
||||
err := c.cc.Invoke(ctx, AgentService_DeleteAgentJobDefinition_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *agentServiceClient) ListAgentJobDefinitions(ctx context.Context, in *ListAgentJobDefinitionsRequesst, opts ...grpc.CallOption) (*ListAgentJobDefinitionsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(ListAgentJobDefinitionsResponse)
|
||||
err := c.cc.Invoke(ctx, AgentService_ListAgentJobDefinitions_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AgentServiceServer is the server API for AgentService service.
|
||||
// All implementations must embed UnimplementedAgentServiceServer
|
||||
// for forward compatibility
|
||||
type AgentServiceServer interface {
|
||||
CreateAgentJobDefinition(context.Context, *CreateAgentJobDefinitionRequest) (*JobDefinition, error)
|
||||
DeleteAgentJobDefinition(context.Context, *DeleteAgentJobDefinitionRequest) (*JobDefinition, error)
|
||||
ListAgentJobDefinitions(context.Context, *ListAgentJobDefinitionsRequesst) (*ListAgentJobDefinitionsResponse, error)
|
||||
mustEmbedUnimplementedAgentServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedAgentServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedAgentServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedAgentServiceServer) CreateAgentJobDefinition(context.Context, *CreateAgentJobDefinitionRequest) (*JobDefinition, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CreateAgentJobDefinition not implemented")
|
||||
}
|
||||
func (UnimplementedAgentServiceServer) DeleteAgentJobDefinition(context.Context, *DeleteAgentJobDefinitionRequest) (*JobDefinition, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteAgentJobDefinition not implemented")
|
||||
}
|
||||
func (UnimplementedAgentServiceServer) ListAgentJobDefinitions(context.Context, *ListAgentJobDefinitionsRequesst) (*ListAgentJobDefinitionsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListAgentJobDefinitions not implemented")
|
||||
}
|
||||
func (UnimplementedAgentServiceServer) mustEmbedUnimplementedAgentServiceServer() {}
|
||||
|
||||
// UnsafeAgentServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to AgentServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeAgentServiceServer interface {
|
||||
mustEmbedUnimplementedAgentServiceServer()
|
||||
}
|
||||
|
||||
func RegisterAgentServiceServer(s grpc.ServiceRegistrar, srv AgentServiceServer) {
|
||||
s.RegisterService(&AgentService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _AgentService_CreateAgentJobDefinition_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CreateAgentJobDefinitionRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AgentServiceServer).CreateAgentJobDefinition(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: AgentService_CreateAgentJobDefinition_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AgentServiceServer).CreateAgentJobDefinition(ctx, req.(*CreateAgentJobDefinitionRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AgentService_DeleteAgentJobDefinition_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DeleteAgentJobDefinitionRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AgentServiceServer).DeleteAgentJobDefinition(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: AgentService_DeleteAgentJobDefinition_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AgentServiceServer).DeleteAgentJobDefinition(ctx, req.(*DeleteAgentJobDefinitionRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AgentService_ListAgentJobDefinitions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListAgentJobDefinitionsRequesst)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AgentServiceServer).ListAgentJobDefinitions(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: AgentService_ListAgentJobDefinitions_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AgentServiceServer).ListAgentJobDefinitions(ctx, req.(*ListAgentJobDefinitionsRequesst))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// AgentService_ServiceDesc is the grpc.ServiceDesc for AgentService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var AgentService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "livekit.AgentService",
|
||||
HandlerType: (*AgentServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "CreateAgentJobDefinition",
|
||||
Handler: _AgentService_CreateAgentJobDefinition_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteAgentJobDefinition",
|
||||
Handler: _AgentService_DeleteAgentJobDefinition_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListAgentJobDefinitions",
|
||||
Handler: _AgentService_ListAgentJobDefinitions_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "livekit_agent.proto",
|
||||
}
|
||||
1759
livekit/livekit_analytics.pb.go
Normal file
1759
livekit/livekit_analytics.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
4943
livekit/livekit_egress.pb.go
Normal file
4943
livekit/livekit_egress.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
3515
livekit/livekit_egress.twirp.go
Normal file
3515
livekit/livekit_egress.twirp.go
Normal file
File diff suppressed because it is too large
Load Diff
1986
livekit/livekit_ingress.pb.go
Normal file
1986
livekit/livekit_ingress.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
1451
livekit/livekit_ingress.twirp.go
Normal file
1451
livekit/livekit_ingress.twirp.go
Normal file
File diff suppressed because it is too large
Load Diff
1412
livekit/livekit_internal.pb.go
Normal file
1412
livekit/livekit_internal.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
4545
livekit/livekit_models.pb.go
Normal file
4545
livekit/livekit_models.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
1898
livekit/livekit_room.pb.go
Normal file
1898
livekit/livekit_room.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
3405
livekit/livekit_room.twirp.go
Normal file
3405
livekit/livekit_room.twirp.go
Normal file
File diff suppressed because it is too large
Load Diff
4907
livekit/livekit_rtc.pb.go
Normal file
4907
livekit/livekit_rtc.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
2569
livekit/livekit_sip.pb.go
Normal file
2569
livekit/livekit_sip.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
3408
livekit/livekit_sip.twirp.go
Normal file
3408
livekit/livekit_sip.twirp.go
Normal file
File diff suppressed because it is too large
Load Diff
271
livekit/livekit_webhook.pb.go
Normal file
271
livekit/livekit_webhook.pb.go
Normal file
@@ -0,0 +1,271 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.2
|
||||
// protoc v4.23.4
|
||||
// source: livekit_webhook.proto
|
||||
|
||||
package livekit
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type WebhookEvent struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// one of room_started, room_finished, participant_joined, participant_left,
|
||||
// track_published, track_unpublished, egress_started, egress_updated, egress_ended,
|
||||
// ingress_started, ingress_ended
|
||||
Event string `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
|
||||
Room *Room `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"`
|
||||
// set when event is participant_* or track_*
|
||||
Participant *ParticipantInfo `protobuf:"bytes,3,opt,name=participant,proto3" json:"participant,omitempty"`
|
||||
// set when event is egress_*
|
||||
EgressInfo *EgressInfo `protobuf:"bytes,9,opt,name=egress_info,json=egressInfo,proto3" json:"egress_info,omitempty"`
|
||||
// set when event is ingress_*
|
||||
IngressInfo *IngressInfo `protobuf:"bytes,10,opt,name=ingress_info,json=ingressInfo,proto3" json:"ingress_info,omitempty"`
|
||||
// set when event is track_*
|
||||
Track *TrackInfo `protobuf:"bytes,8,opt,name=track,proto3" json:"track,omitempty"`
|
||||
// unique event uuid
|
||||
Id string `protobuf:"bytes,6,opt,name=id,proto3" json:"id,omitempty"`
|
||||
// timestamp in seconds
|
||||
CreatedAt int64 `protobuf:"varint,7,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
||||
NumDropped int32 `protobuf:"varint,11,opt,name=num_dropped,json=numDropped,proto3" json:"num_dropped,omitempty"`
|
||||
}
|
||||
|
||||
func (x *WebhookEvent) Reset() {
|
||||
*x = WebhookEvent{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_livekit_webhook_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *WebhookEvent) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*WebhookEvent) ProtoMessage() {}
|
||||
|
||||
func (x *WebhookEvent) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_livekit_webhook_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use WebhookEvent.ProtoReflect.Descriptor instead.
|
||||
func (*WebhookEvent) Descriptor() ([]byte, []int) {
|
||||
return file_livekit_webhook_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *WebhookEvent) GetEvent() string {
|
||||
if x != nil {
|
||||
return x.Event
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *WebhookEvent) GetRoom() *Room {
|
||||
if x != nil {
|
||||
return x.Room
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WebhookEvent) GetParticipant() *ParticipantInfo {
|
||||
if x != nil {
|
||||
return x.Participant
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WebhookEvent) GetEgressInfo() *EgressInfo {
|
||||
if x != nil {
|
||||
return x.EgressInfo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WebhookEvent) GetIngressInfo() *IngressInfo {
|
||||
if x != nil {
|
||||
return x.IngressInfo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WebhookEvent) GetTrack() *TrackInfo {
|
||||
if x != nil {
|
||||
return x.Track
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WebhookEvent) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *WebhookEvent) GetCreatedAt() int64 {
|
||||
if x != nil {
|
||||
return x.CreatedAt
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *WebhookEvent) GetNumDropped() int32 {
|
||||
if x != nil {
|
||||
return x.NumDropped
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_livekit_webhook_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_livekit_webhook_proto_rawDesc = []byte{
|
||||
0x0a, 0x15, 0x6c, 0x69, 0x76, 0x65, 0x6b, 0x69, 0x74, 0x5f, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f,
|
||||
0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6c, 0x69, 0x76, 0x65, 0x6b, 0x69, 0x74,
|
||||
0x1a, 0x14, 0x6c, 0x69, 0x76, 0x65, 0x6b, 0x69, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x6c, 0x69, 0x76, 0x65, 0x6b, 0x69, 0x74, 0x5f,
|
||||
0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x6c, 0x69,
|
||||
0x76, 0x65, 0x6b, 0x69, 0x74, 0x5f, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x22, 0xec, 0x02, 0x0a, 0x0c, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x45,
|
||||
0x76, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x04, 0x72, 0x6f,
|
||||
0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x69, 0x76, 0x65, 0x6b,
|
||||
0x69, 0x74, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x3a, 0x0a,
|
||||
0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x76, 0x65, 0x6b, 0x69, 0x74, 0x2e, 0x50, 0x61, 0x72,
|
||||
0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0b, 0x70, 0x61,
|
||||
0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x12, 0x34, 0x0a, 0x0b, 0x65, 0x67, 0x72,
|
||||
0x65, 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13,
|
||||
0x2e, 0x6c, 0x69, 0x76, 0x65, 0x6b, 0x69, 0x74, 0x2e, 0x45, 0x67, 0x72, 0x65, 0x73, 0x73, 0x49,
|
||||
0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12,
|
||||
0x37, 0x0a, 0x0c, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18,
|
||||
0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x69, 0x76, 0x65, 0x6b, 0x69, 0x74, 0x2e,
|
||||
0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0b, 0x69, 0x6e, 0x67,
|
||||
0x72, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x28, 0x0a, 0x05, 0x74, 0x72, 0x61, 0x63,
|
||||
0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x69, 0x76, 0x65, 0x6b, 0x69,
|
||||
0x74, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x74, 0x72, 0x61,
|
||||
0x63, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
|
||||
0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74,
|
||||
0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41,
|
||||
0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64,
|
||||
0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x44, 0x72, 0x6f, 0x70, 0x70,
|
||||
0x65, 0x64, 0x42, 0x46, 0x5a, 0x23, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x2f, 0x6c, 0x69, 0x76, 0x65, 0x6b, 0x69, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
|
||||
0x6c, 0x2f, 0x6c, 0x69, 0x76, 0x65, 0x6b, 0x69, 0x74, 0xaa, 0x02, 0x0d, 0x4c, 0x69, 0x76, 0x65,
|
||||
0x4b, 0x69, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0xea, 0x02, 0x0e, 0x4c, 0x69, 0x76, 0x65,
|
||||
0x4b, 0x69, 0x74, 0x3a, 0x3a, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_livekit_webhook_proto_rawDescOnce sync.Once
|
||||
file_livekit_webhook_proto_rawDescData = file_livekit_webhook_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_livekit_webhook_proto_rawDescGZIP() []byte {
|
||||
file_livekit_webhook_proto_rawDescOnce.Do(func() {
|
||||
file_livekit_webhook_proto_rawDescData = protoimpl.X.CompressGZIP(file_livekit_webhook_proto_rawDescData)
|
||||
})
|
||||
return file_livekit_webhook_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_livekit_webhook_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_livekit_webhook_proto_goTypes = []any{
|
||||
(*WebhookEvent)(nil), // 0: livekit.WebhookEvent
|
||||
(*Room)(nil), // 1: livekit.Room
|
||||
(*ParticipantInfo)(nil), // 2: livekit.ParticipantInfo
|
||||
(*EgressInfo)(nil), // 3: livekit.EgressInfo
|
||||
(*IngressInfo)(nil), // 4: livekit.IngressInfo
|
||||
(*TrackInfo)(nil), // 5: livekit.TrackInfo
|
||||
}
|
||||
var file_livekit_webhook_proto_depIdxs = []int32{
|
||||
1, // 0: livekit.WebhookEvent.room:type_name -> livekit.Room
|
||||
2, // 1: livekit.WebhookEvent.participant:type_name -> livekit.ParticipantInfo
|
||||
3, // 2: livekit.WebhookEvent.egress_info:type_name -> livekit.EgressInfo
|
||||
4, // 3: livekit.WebhookEvent.ingress_info:type_name -> livekit.IngressInfo
|
||||
5, // 4: livekit.WebhookEvent.track:type_name -> livekit.TrackInfo
|
||||
5, // [5:5] is the sub-list for method output_type
|
||||
5, // [5:5] is the sub-list for method input_type
|
||||
5, // [5:5] is the sub-list for extension type_name
|
||||
5, // [5:5] is the sub-list for extension extendee
|
||||
0, // [0:5] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_livekit_webhook_proto_init() }
|
||||
func file_livekit_webhook_proto_init() {
|
||||
if File_livekit_webhook_proto != nil {
|
||||
return
|
||||
}
|
||||
file_livekit_models_proto_init()
|
||||
file_livekit_egress_proto_init()
|
||||
file_livekit_ingress_proto_init()
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_livekit_webhook_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*WebhookEvent); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_livekit_webhook_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_livekit_webhook_proto_goTypes,
|
||||
DependencyIndexes: file_livekit_webhook_proto_depIdxs,
|
||||
MessageInfos: file_livekit_webhook_proto_msgTypes,
|
||||
}.Build()
|
||||
File_livekit_webhook_proto = out.File
|
||||
file_livekit_webhook_proto_rawDesc = nil
|
||||
file_livekit_webhook_proto_goTypes = nil
|
||||
file_livekit_webhook_proto_depIdxs = nil
|
||||
}
|
||||
96
livekit/sip.go
Normal file
96
livekit/sip.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package livekit
|
||||
|
||||
// ToProto implements DataPacket in Go SDK.
|
||||
func (p *SipDTMF) ToProto() *DataPacket {
|
||||
return &DataPacket{
|
||||
Value: &DataPacket_SipDtmf{
|
||||
SipDtmf: p,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// AsInbound converts legacy SIPTrunkInfo to SIPInboundTrunkInfo.
|
||||
func (p *SIPTrunkInfo) AsInbound() *SIPInboundTrunkInfo {
|
||||
if p == nil || p.Kind == SIPTrunkInfo_TRUNK_OUTBOUND {
|
||||
return nil
|
||||
}
|
||||
var nums []string
|
||||
if p.OutboundNumber != "" {
|
||||
nums = []string{p.OutboundNumber}
|
||||
}
|
||||
return &SIPInboundTrunkInfo{
|
||||
SipTrunkId: p.SipTrunkId,
|
||||
Name: p.Name,
|
||||
Metadata: p.Metadata,
|
||||
Numbers: nums,
|
||||
AllowedAddresses: p.InboundAddresses,
|
||||
AllowedNumbers: p.InboundNumbers,
|
||||
AuthUsername: p.InboundUsername,
|
||||
AuthPassword: p.InboundPassword,
|
||||
}
|
||||
}
|
||||
|
||||
// AsTrunkInfo converts SIPInboundTrunkInfo to legacy SIPTrunkInfo.
|
||||
func (p *SIPInboundTrunkInfo) AsTrunkInfo() *SIPTrunkInfo {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
var num string
|
||||
if len(p.Numbers) != 0 {
|
||||
num = p.Numbers[0]
|
||||
}
|
||||
return &SIPTrunkInfo{
|
||||
SipTrunkId: p.SipTrunkId,
|
||||
Kind: SIPTrunkInfo_TRUNK_INBOUND,
|
||||
Name: p.Name,
|
||||
Metadata: p.Metadata,
|
||||
OutboundNumber: num,
|
||||
InboundAddresses: p.AllowedAddresses,
|
||||
InboundNumbers: p.AllowedNumbers,
|
||||
InboundUsername: p.AuthUsername,
|
||||
InboundPassword: p.AuthPassword,
|
||||
}
|
||||
}
|
||||
|
||||
// AsOutbound converts legacy SIPTrunkInfo to SIPOutboundTrunkInfo.
|
||||
func (p *SIPTrunkInfo) AsOutbound() *SIPOutboundTrunkInfo {
|
||||
if p == nil || p.Kind == SIPTrunkInfo_TRUNK_INBOUND {
|
||||
return nil
|
||||
}
|
||||
var nums []string
|
||||
if p.OutboundNumber != "" {
|
||||
nums = []string{p.OutboundNumber}
|
||||
}
|
||||
return &SIPOutboundTrunkInfo{
|
||||
SipTrunkId: p.SipTrunkId,
|
||||
Name: p.Name,
|
||||
Metadata: p.Metadata,
|
||||
Address: p.OutboundAddress,
|
||||
Transport: p.Transport,
|
||||
Numbers: nums,
|
||||
AuthUsername: p.OutboundUsername,
|
||||
AuthPassword: p.OutboundPassword,
|
||||
}
|
||||
}
|
||||
|
||||
// AsTrunkInfo converts SIPOutboundTrunkInfo to legacy SIPTrunkInfo.
|
||||
func (p *SIPOutboundTrunkInfo) AsTrunkInfo() *SIPTrunkInfo {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
var num string
|
||||
if len(p.Numbers) != 0 {
|
||||
num = p.Numbers[0]
|
||||
}
|
||||
return &SIPTrunkInfo{
|
||||
SipTrunkId: p.SipTrunkId,
|
||||
Kind: SIPTrunkInfo_TRUNK_OUTBOUND,
|
||||
Name: p.Name,
|
||||
Metadata: p.Metadata,
|
||||
OutboundAddress: p.Address,
|
||||
Transport: p.Transport,
|
||||
OutboundNumber: num,
|
||||
OutboundUsername: p.AuthUsername,
|
||||
OutboundPassword: p.AuthPassword,
|
||||
}
|
||||
}
|
||||
37
livekit/sip_test.go
Normal file
37
livekit/sip_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package livekit
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSIPTrunkAs(t *testing.T) {
|
||||
t.Run("inbound", func(t *testing.T) {
|
||||
in := &SIPInboundTrunkInfo{
|
||||
SipTrunkId: "id",
|
||||
Name: "name",
|
||||
Metadata: "{}",
|
||||
Numbers: []string{"+1"},
|
||||
AllowedAddresses: []string{"sip.com"},
|
||||
AllowedNumbers: []string{"+2", "+3"},
|
||||
AuthUsername: "user",
|
||||
AuthPassword: "pass",
|
||||
}
|
||||
got := in.AsTrunkInfo().AsInbound()
|
||||
require.Equal(t, in, got)
|
||||
})
|
||||
t.Run("outbound", func(t *testing.T) {
|
||||
out := &SIPOutboundTrunkInfo{
|
||||
SipTrunkId: "id",
|
||||
Name: "name",
|
||||
Metadata: "{}",
|
||||
Address: "sip.com",
|
||||
Transport: 1,
|
||||
Numbers: []string{"+1"},
|
||||
AuthUsername: "user",
|
||||
AuthPassword: "pass",
|
||||
}
|
||||
got := out.AsTrunkInfo().AsOutbound()
|
||||
require.Equal(t, out, got)
|
||||
})
|
||||
}
|
||||
84
livekit/types.go
Normal file
84
livekit/types.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package livekit
|
||||
|
||||
import (
|
||||
"github.com/bufbuild/protoyaml-go"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type TrackID string
|
||||
type ParticipantID string
|
||||
type ParticipantIdentity string
|
||||
type ParticipantName string
|
||||
type RoomID string
|
||||
type RoomName string
|
||||
type ConnectionID string
|
||||
type NodeID string
|
||||
type RoomKey struct {
|
||||
ProjectID string
|
||||
RoomName RoomName
|
||||
}
|
||||
type ParticipantKey struct {
|
||||
RoomKey
|
||||
Identity ParticipantIdentity
|
||||
}
|
||||
|
||||
type stringTypes interface {
|
||||
ParticipantID | RoomID | TrackID | ParticipantIdentity | ParticipantName | RoomName | ConnectionID | NodeID
|
||||
}
|
||||
|
||||
func IDsAsStrings[T stringTypes](ids []T) []string {
|
||||
strs := make([]string, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
strs = append(strs, string(id))
|
||||
}
|
||||
return strs
|
||||
}
|
||||
|
||||
func StringsAsIDs[T stringTypes](ids []string) []T {
|
||||
asID := make([]T, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
asID = append(asID, T(id))
|
||||
}
|
||||
|
||||
return asID
|
||||
}
|
||||
|
||||
type Guid interface {
|
||||
TrackID | ParticipantID | RoomID
|
||||
}
|
||||
|
||||
type GuidBlock [9]byte
|
||||
|
||||
func (r *RoomEgress) UnmarshalYAML(value *yaml.Node) error {
|
||||
// Marshall the Node back to yaml to pass it to the protobuf specific unmarshaller
|
||||
str, err := yaml.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return protoyaml.Unmarshal(str, r)
|
||||
}
|
||||
|
||||
func (r *RoomAgent) UnmarshalYAML(value *yaml.Node) error {
|
||||
// Marshall the Node back to yaml to pass it to the protobuf specific unmarshaller
|
||||
str, err := yaml.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return protoyaml.Unmarshal(str, r)
|
||||
}
|
||||
2
livekit/types_test
Normal file
2
livekit/types_test
Normal file
@@ -0,0 +1,2 @@
|
||||
func TestUnmarshallRoomEgress(t testing.T) {
|
||||
}
|
||||
66
livekit/types_test.go
Normal file
66
livekit/types_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package livekit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestUnmarshallRoomEgress(t *testing.T) {
|
||||
y := `
|
||||
a:
|
||||
room:
|
||||
room_name: room name
|
||||
b:
|
||||
participant:
|
||||
file_outputs:
|
||||
- s3:
|
||||
access_key: key
|
||||
`
|
||||
|
||||
obj := make(map[string]*RoomEgress)
|
||||
|
||||
err := yaml.Unmarshal([]byte(y), &obj)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(obj))
|
||||
|
||||
re := obj["a"]
|
||||
require.NotNil(t, re)
|
||||
require.Equal(t, re.Room.RoomName, "room name")
|
||||
|
||||
re = obj["b"]
|
||||
require.NotNil(t, re)
|
||||
require.Equal(t, 1, len(re.Participant.FileOutputs))
|
||||
require.Equal(t, "key", re.Participant.FileOutputs[0].Output.(*EncodedFileOutput_S3).S3.AccessKey)
|
||||
}
|
||||
|
||||
func TestUnmarshallRoomAgent(t *testing.T) {
|
||||
y := `
|
||||
a:
|
||||
agents:
|
||||
- type: 0
|
||||
room: test room
|
||||
- type: 1
|
||||
participant_identity:
|
||||
- id1
|
||||
- id2
|
||||
namespace: nm
|
||||
`
|
||||
obj := make(map[string]*RoomAgent)
|
||||
|
||||
err := yaml.Unmarshal([]byte(y), &obj)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(obj))
|
||||
|
||||
re := obj["a"]
|
||||
require.NotNil(t, re)
|
||||
require.Equal(t, len(re.Agents), 2)
|
||||
require.Equal(t, JobType_JT_ROOM, re.Agents[0].Type)
|
||||
require.Equal(t, "test room", re.Agents[0].Room)
|
||||
require.Equal(t, JobType_JT_PUBLISHER, re.Agents[1].Type)
|
||||
require.Equal(t, "nm", re.Agents[1].Namespace)
|
||||
require.Equal(t, 2, len(re.Agents[1].ParticipantIdentity))
|
||||
require.Equal(t, "id1", re.Agents[1].ParticipantIdentity[0])
|
||||
require.Equal(t, "id2", re.Agents[1].ParticipantIdentity[1])
|
||||
}
|
||||
74
logger/config.go
Normal file
74
logger/config.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logger
|
||||
|
||||
import "sync"
|
||||
|
||||
type Config struct {
|
||||
JSON bool `yaml:"json,omitempty"`
|
||||
Level string `yaml:"level,omitempty"`
|
||||
// true to enable log sampling, where the same log message and level will be throttled.
|
||||
// we have two layers of sampling
|
||||
// 1. global sampling - within a second, it will log the first SampleInitial, then every SampleInterval messages.
|
||||
// 2. per participant/track sampling - to be used with Logger.WithItemSampler(). This would be used to throttle
|
||||
// the logs for a particular participant/track.
|
||||
Sample bool `yaml:"sample,omitempty"`
|
||||
|
||||
ComponentLevels map[string]string `yaml:"component_levels,omitempty"`
|
||||
|
||||
// global sampling per server
|
||||
// when sampling, the first N logs will be logged
|
||||
SampleInitial int `yaml:"sample_initial,omitempty"`
|
||||
// when sampling, every Mth log will be logged
|
||||
SampleInterval int `yaml:"sample_interval,omitempty"`
|
||||
|
||||
// participant/track level sampling
|
||||
ItemSampleSeconds int `yaml:"item_sample_seconds,omitempty"`
|
||||
ItemSampleInitial int `yaml:"item_sample_initial,omitempty"`
|
||||
ItemSampleInterval int `yaml:"item_sample_interval,omitempty"`
|
||||
|
||||
lock sync.Mutex `yaml:"-"`
|
||||
onUpdatedCallbacks []ConfigObserver `yaml:"-"`
|
||||
}
|
||||
|
||||
type ConfigObserver func(*Config) error
|
||||
|
||||
func (c *Config) Update(o *Config) error {
|
||||
c.lock.Lock()
|
||||
c.JSON = o.JSON
|
||||
c.Level = o.Level
|
||||
c.Sample = o.Sample
|
||||
c.SampleInitial = o.SampleInitial
|
||||
c.SampleInterval = o.SampleInterval
|
||||
c.ItemSampleSeconds = o.ItemSampleSeconds
|
||||
c.ItemSampleInitial = o.ItemSampleInitial
|
||||
c.ItemSampleInterval = o.ItemSampleInterval
|
||||
c.ComponentLevels = o.ComponentLevels
|
||||
callbacks := c.onUpdatedCallbacks
|
||||
c.lock.Unlock()
|
||||
|
||||
for _, cb := range callbacks {
|
||||
if err := cb(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) AddUpdateObserver(cb ConfigObserver) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.onUpdatedCallbacks = append(c.onUpdatedCallbacks, cb)
|
||||
}
|
||||
442
logger/logger.go
Normal file
442
logger/logger.go
Normal file
@@ -0,0 +1,442 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/livekit/protocol/logger/zaputil"
|
||||
)
|
||||
|
||||
var (
|
||||
discardLogger = logr.Discard()
|
||||
defaultLogger Logger = LogRLogger(discardLogger)
|
||||
pkgLogger Logger = LogRLogger(discardLogger)
|
||||
)
|
||||
|
||||
// InitFromConfig initializes a Zap-based logger
|
||||
func InitFromConfig(conf *Config, name string) {
|
||||
l, err := NewZapLogger(conf)
|
||||
if err == nil {
|
||||
SetLogger(l, name)
|
||||
slog.SetDefault(slog.New(ToSlogHandler(l)))
|
||||
}
|
||||
}
|
||||
|
||||
// GetLogger returns the logger that was set with SetLogger with an extra depth of 1
|
||||
func GetLogger() Logger {
|
||||
return defaultLogger
|
||||
}
|
||||
|
||||
// SetLogger lets you use a custom logger. Pass in a logr.Logger with default depth
|
||||
func SetLogger(l Logger, name string) {
|
||||
defaultLogger = l.WithCallDepth(1).WithName(name)
|
||||
// pkg wrapper needs to drop two levels of depth
|
||||
pkgLogger = l.WithCallDepth(2).WithName(name)
|
||||
}
|
||||
|
||||
func Debugw(msg string, keysAndValues ...interface{}) {
|
||||
pkgLogger.Debugw(msg, keysAndValues...)
|
||||
}
|
||||
|
||||
func Infow(msg string, keysAndValues ...interface{}) {
|
||||
pkgLogger.Infow(msg, keysAndValues...)
|
||||
}
|
||||
|
||||
func Warnw(msg string, err error, keysAndValues ...interface{}) {
|
||||
pkgLogger.Warnw(msg, err, keysAndValues...)
|
||||
}
|
||||
|
||||
func Errorw(msg string, err error, keysAndValues ...interface{}) {
|
||||
pkgLogger.Errorw(msg, err, keysAndValues...)
|
||||
}
|
||||
|
||||
func ParseZapLevel(level string) zapcore.Level {
|
||||
lvl := zapcore.InfoLevel
|
||||
if level != "" {
|
||||
_ = lvl.UnmarshalText([]byte(level))
|
||||
}
|
||||
return lvl
|
||||
}
|
||||
|
||||
type DeferredFieldResolver = zaputil.DeferredFieldResolver
|
||||
|
||||
type Logger interface {
|
||||
Debugw(msg string, keysAndValues ...interface{})
|
||||
Infow(msg string, keysAndValues ...interface{})
|
||||
Warnw(msg string, err error, keysAndValues ...interface{})
|
||||
Errorw(msg string, err error, keysAndValues ...interface{})
|
||||
WithValues(keysAndValues ...interface{}) Logger
|
||||
WithName(name string) Logger
|
||||
// WithComponent creates a new logger with name as "<name>.<component>", and uses a log level as specified
|
||||
WithComponent(component string) Logger
|
||||
WithCallDepth(depth int) Logger
|
||||
WithItemSampler() Logger
|
||||
// WithoutSampler returns the original logger without sampling
|
||||
WithoutSampler() Logger
|
||||
WithDeferredValues() (Logger, DeferredFieldResolver)
|
||||
}
|
||||
|
||||
type sharedConfig struct {
|
||||
level zap.AtomicLevel
|
||||
mu sync.Mutex
|
||||
componentLevels map[string]zap.AtomicLevel
|
||||
config *Config
|
||||
}
|
||||
|
||||
func newSharedConfig(conf *Config) *sharedConfig {
|
||||
sc := &sharedConfig{
|
||||
level: zap.NewAtomicLevelAt(ParseZapLevel(conf.Level)),
|
||||
config: conf,
|
||||
componentLevels: make(map[string]zap.AtomicLevel),
|
||||
}
|
||||
conf.AddUpdateObserver(sc.onConfigUpdate)
|
||||
_ = sc.onConfigUpdate(conf)
|
||||
return sc
|
||||
}
|
||||
|
||||
func (c *sharedConfig) onConfigUpdate(conf *Config) error {
|
||||
// update log levels
|
||||
c.level.SetLevel(ParseZapLevel(conf.Level))
|
||||
|
||||
// we have to update alla existing component levels
|
||||
c.mu.Lock()
|
||||
c.config = conf
|
||||
for component, atomicLevel := range c.componentLevels {
|
||||
effectiveLevel := c.level.Level()
|
||||
parts := strings.Split(component, ".")
|
||||
confSearch:
|
||||
for len(parts) > 0 {
|
||||
search := strings.Join(parts, ".")
|
||||
if compLevel, ok := conf.ComponentLevels[search]; ok {
|
||||
effectiveLevel = ParseZapLevel(compLevel)
|
||||
break confSearch
|
||||
}
|
||||
parts = parts[:len(parts)-1]
|
||||
}
|
||||
atomicLevel.SetLevel(effectiveLevel)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensure we have an atomic level in the map representing the full component path
|
||||
// this makes it possible to update the log level after the fact
|
||||
func (c *sharedConfig) ComponentLevel(component string) zap.AtomicLevel {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if compLevel, ok := c.componentLevels[component]; ok {
|
||||
return compLevel
|
||||
}
|
||||
|
||||
// search up the hierarchy to find the first level that is set
|
||||
atomicLevel := zap.NewAtomicLevelAt(c.level.Level())
|
||||
c.componentLevels[component] = atomicLevel
|
||||
parts := strings.Split(component, ".")
|
||||
for len(parts) > 0 {
|
||||
search := strings.Join(parts, ".")
|
||||
if compLevel, ok := c.config.ComponentLevels[search]; ok {
|
||||
atomicLevel.SetLevel(ParseZapLevel(compLevel))
|
||||
return atomicLevel
|
||||
}
|
||||
parts = parts[:len(parts)-1]
|
||||
}
|
||||
return atomicLevel
|
||||
}
|
||||
|
||||
type zapConfig struct {
|
||||
conf *Config
|
||||
sc *sharedConfig
|
||||
writeEnablers *xsync.MapOf[string, *zaputil.WriteEnabler]
|
||||
levelEnablers *xsync.MapOf[string, *zaputil.OrLevelEnabler]
|
||||
tap *zaputil.WriteEnabler
|
||||
}
|
||||
|
||||
type ZapLoggerOption func(*zapConfig)
|
||||
|
||||
func WithTap(tap *zaputil.WriteEnabler) ZapLoggerOption {
|
||||
return func(zc *zapConfig) {
|
||||
zc.tap = tap
|
||||
}
|
||||
}
|
||||
|
||||
type ZapComponentLeveler interface {
|
||||
ComponentLevel(component string) zapcore.LevelEnabler
|
||||
}
|
||||
|
||||
type ZapLogger interface {
|
||||
Logger
|
||||
ToZap() *zap.SugaredLogger
|
||||
ComponentLeveler() ZapComponentLeveler
|
||||
WithMinLevel(lvl zapcore.LevelEnabler) Logger
|
||||
}
|
||||
|
||||
type zapLogger[T zaputil.Encoder[T]] struct {
|
||||
zap *zap.SugaredLogger
|
||||
*zapConfig
|
||||
enc T
|
||||
component string
|
||||
deferred []*zaputil.Deferrer
|
||||
sampler *zaputil.Sampler
|
||||
minLevel zapcore.LevelEnabler
|
||||
}
|
||||
|
||||
func FromZapLogger(log *zap.Logger, conf *Config, opts ...ZapLoggerOption) (ZapLogger, error) {
|
||||
if log == nil {
|
||||
log = zap.New(nil).WithOptions(zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))
|
||||
}
|
||||
zap := log.Sugar()
|
||||
|
||||
zc := &zapConfig{
|
||||
conf: conf,
|
||||
sc: newSharedConfig(conf),
|
||||
writeEnablers: xsync.NewMapOf[string, *zaputil.WriteEnabler](),
|
||||
levelEnablers: xsync.NewMapOf[string, *zaputil.OrLevelEnabler](),
|
||||
tap: zaputil.NewDiscardWriteEnabler(),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(zc)
|
||||
}
|
||||
|
||||
var sampler *zaputil.Sampler
|
||||
if conf.Sample {
|
||||
var initial = 20
|
||||
var interval = 100
|
||||
if conf.ItemSampleInitial != 0 {
|
||||
initial = conf.ItemSampleInitial
|
||||
}
|
||||
if conf.ItemSampleInterval != 0 {
|
||||
interval = conf.ItemSampleInterval
|
||||
}
|
||||
sampler = zaputil.NewSampler(time.Second, initial, interval)
|
||||
}
|
||||
|
||||
if conf.JSON {
|
||||
return newZapLogger(zap, zc, zaputil.NewProductionEncoder(), sampler), nil
|
||||
} else {
|
||||
return newZapLogger(zap, zc, zaputil.NewDevelopmentEncoder(), sampler), nil
|
||||
}
|
||||
}
|
||||
|
||||
func NewZapLogger(conf *Config, opts ...ZapLoggerOption) (ZapLogger, error) {
|
||||
return FromZapLogger(nil, conf, opts...)
|
||||
}
|
||||
|
||||
func newZapLogger[T zaputil.Encoder[T]](zap *zap.SugaredLogger, zc *zapConfig, enc T, sampler *zaputil.Sampler) ZapLogger {
|
||||
l := &zapLogger[T]{
|
||||
zap: zap,
|
||||
zapConfig: zc,
|
||||
enc: enc,
|
||||
sampler: sampler,
|
||||
}
|
||||
l.zap = l.ToZap()
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *zapLogger[T]) ToZap() *zap.SugaredLogger {
|
||||
var console *zaputil.WriteEnabler
|
||||
if l.minLevel == nil {
|
||||
console, _ = l.writeEnablers.LoadOrCompute(l.component, func() *zaputil.WriteEnabler {
|
||||
return zaputil.NewWriteEnabler(os.Stderr, l.sc.ComponentLevel(l.component))
|
||||
})
|
||||
} else {
|
||||
enab := zaputil.OrLevelEnabler{l.minLevel, l.sc.ComponentLevel(l.component)}
|
||||
console = zaputil.NewWriteEnabler(os.Stderr, enab)
|
||||
}
|
||||
|
||||
c := l.enc.Core(console, l.tap)
|
||||
for i := range l.deferred {
|
||||
c = zaputil.NewDeferredValueCore(c, l.deferred[i])
|
||||
}
|
||||
if l.sampler != nil {
|
||||
c = zaputil.NewSamplerCore(c, l.sampler)
|
||||
}
|
||||
|
||||
return l.zap.WithOptions(zap.WrapCore(func(zapcore.Core) zapcore.Core { return c }))
|
||||
}
|
||||
|
||||
type zapLoggerComponentLeveler[T zaputil.Encoder[T]] struct {
|
||||
zl *zapLogger[T]
|
||||
}
|
||||
|
||||
func (l zapLoggerComponentLeveler[T]) ComponentLevel(component string) zapcore.LevelEnabler {
|
||||
if l.zl.component != "" {
|
||||
component = l.zl.component + "." + component
|
||||
}
|
||||
|
||||
enab, _ := l.zl.levelEnablers.LoadOrCompute(component, func() *zaputil.OrLevelEnabler {
|
||||
return &zaputil.OrLevelEnabler{l.zl.sc.ComponentLevel(component), l.zl.tap}
|
||||
})
|
||||
return enab
|
||||
}
|
||||
|
||||
func (l *zapLogger[T]) ComponentLeveler() ZapComponentLeveler {
|
||||
return zapLoggerComponentLeveler[T]{l}
|
||||
}
|
||||
|
||||
func (l *zapLogger[T]) Debugw(msg string, keysAndValues ...interface{}) {
|
||||
l.zap.Debugw(msg, keysAndValues...)
|
||||
}
|
||||
|
||||
func (l *zapLogger[T]) WithMinLevel(lvl zapcore.LevelEnabler) Logger {
|
||||
dup := *l
|
||||
dup.minLevel = lvl
|
||||
dup.zap = dup.ToZap()
|
||||
return &dup
|
||||
}
|
||||
|
||||
func (l *zapLogger[T]) Infow(msg string, keysAndValues ...interface{}) {
|
||||
l.zap.Infow(msg, keysAndValues...)
|
||||
}
|
||||
|
||||
func (l *zapLogger[T]) Warnw(msg string, err error, keysAndValues ...interface{}) {
|
||||
if err != nil {
|
||||
keysAndValues = append(keysAndValues, "error", err)
|
||||
}
|
||||
l.zap.Warnw(msg, keysAndValues...)
|
||||
}
|
||||
|
||||
func (l *zapLogger[T]) Errorw(msg string, err error, keysAndValues ...interface{}) {
|
||||
if err != nil {
|
||||
keysAndValues = append(keysAndValues, "error", err)
|
||||
}
|
||||
l.zap.Errorw(msg, keysAndValues...)
|
||||
}
|
||||
|
||||
func (l *zapLogger[T]) WithValues(keysAndValues ...interface{}) Logger {
|
||||
dup := *l
|
||||
dup.enc = dup.enc.WithValues(keysAndValues...)
|
||||
dup.zap = dup.ToZap()
|
||||
return &dup
|
||||
}
|
||||
|
||||
func (l *zapLogger[T]) WithName(name string) Logger {
|
||||
dup := *l
|
||||
dup.zap = dup.zap.Named(name)
|
||||
return &dup
|
||||
}
|
||||
|
||||
func (l *zapLogger[T]) WithComponent(component string) Logger {
|
||||
dup := *l
|
||||
dup.zap = dup.zap.Named(component)
|
||||
if dup.component == "" {
|
||||
dup.component = component
|
||||
} else {
|
||||
dup.component = dup.component + "." + component
|
||||
}
|
||||
dup.zap = dup.ToZap()
|
||||
return &dup
|
||||
}
|
||||
|
||||
func (l *zapLogger[T]) WithCallDepth(depth int) Logger {
|
||||
dup := *l
|
||||
dup.zap = dup.zap.WithOptions(zap.AddCallerSkip(depth))
|
||||
return &dup
|
||||
}
|
||||
|
||||
func (l *zapLogger[T]) WithItemSampler() Logger {
|
||||
if l.conf.ItemSampleSeconds == 0 {
|
||||
return l
|
||||
}
|
||||
dup := *l
|
||||
dup.sampler = zaputil.NewSampler(
|
||||
time.Duration(l.conf.ItemSampleSeconds)*time.Second,
|
||||
l.conf.ItemSampleInitial,
|
||||
l.conf.ItemSampleInterval,
|
||||
)
|
||||
dup.zap = dup.ToZap()
|
||||
return &dup
|
||||
}
|
||||
|
||||
func (l *zapLogger[T]) WithoutSampler() Logger {
|
||||
dup := *l
|
||||
dup.sampler = nil
|
||||
dup.zap = dup.ToZap()
|
||||
return &dup
|
||||
}
|
||||
|
||||
func (l *zapLogger[T]) WithDeferredValues() (Logger, DeferredFieldResolver) {
|
||||
dup := *l
|
||||
def, resolve := zaputil.NewDeferrer()
|
||||
dup.deferred = append(dup.deferred[0:len(dup.deferred):len(dup.deferred)], def)
|
||||
dup.zap = dup.ToZap()
|
||||
return &dup, resolve
|
||||
}
|
||||
|
||||
type LogRLogger logr.Logger
|
||||
|
||||
func (l LogRLogger) toLogr() logr.Logger {
|
||||
if logr.Logger(l).GetSink() == nil {
|
||||
return discardLogger
|
||||
}
|
||||
return logr.Logger(l)
|
||||
}
|
||||
|
||||
func (l LogRLogger) Debugw(msg string, keysAndValues ...interface{}) {
|
||||
l.toLogr().V(1).Info(msg, keysAndValues...)
|
||||
}
|
||||
|
||||
func (l LogRLogger) Infow(msg string, keysAndValues ...interface{}) {
|
||||
l.toLogr().Info(msg, keysAndValues...)
|
||||
}
|
||||
|
||||
func (l LogRLogger) Warnw(msg string, err error, keysAndValues ...interface{}) {
|
||||
if err != nil {
|
||||
keysAndValues = append(keysAndValues, "error", err)
|
||||
}
|
||||
l.toLogr().Info(msg, keysAndValues...)
|
||||
}
|
||||
|
||||
func (l LogRLogger) Errorw(msg string, err error, keysAndValues ...interface{}) {
|
||||
l.toLogr().Error(err, msg, keysAndValues...)
|
||||
}
|
||||
|
||||
func (l LogRLogger) WithValues(keysAndValues ...interface{}) Logger {
|
||||
return LogRLogger(l.toLogr().WithValues(keysAndValues...))
|
||||
}
|
||||
|
||||
func (l LogRLogger) WithName(name string) Logger {
|
||||
return LogRLogger(l.toLogr().WithName(name))
|
||||
}
|
||||
|
||||
func (l LogRLogger) WithComponent(component string) Logger {
|
||||
return LogRLogger(l.toLogr().WithName(component))
|
||||
}
|
||||
|
||||
func (l LogRLogger) WithCallDepth(depth int) Logger {
|
||||
return LogRLogger(l.toLogr().WithCallDepth(depth))
|
||||
}
|
||||
|
||||
func (l LogRLogger) WithItemSampler() Logger {
|
||||
// logr does not support sampling
|
||||
return l
|
||||
}
|
||||
|
||||
func (l LogRLogger) WithoutSampler() Logger {
|
||||
return l
|
||||
}
|
||||
|
||||
func (l LogRLogger) WithDeferredValues() (Logger, DeferredFieldResolver) {
|
||||
return l, func(args ...any) {}
|
||||
}
|
||||
125
logger/logger_test.go
Normal file
125
logger/logger_test.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/livekit/protocol/logger/zaputil"
|
||||
)
|
||||
|
||||
func zapLoggerCore(l Logger) zapcore.Core {
|
||||
return l.(ZapLogger).ToZap().Desugar().Core()
|
||||
}
|
||||
|
||||
func TestLoggerComponent(t *testing.T) {
|
||||
t.Run("inheriting parent level", func(t *testing.T) {
|
||||
l, err := NewZapLogger(&Config{
|
||||
Level: "info",
|
||||
ComponentLevels: map[string]string{
|
||||
"mycomponent": "warn",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
sub := zapLoggerCore(l.WithComponent("sub"))
|
||||
require.True(t, sub.Enabled(zapcore.InfoLevel))
|
||||
require.False(t, sub.Enabled(zapcore.DebugLevel))
|
||||
|
||||
compLogger := zapLoggerCore(l.WithComponent("mycomponent").WithComponent("level2"))
|
||||
require.True(t, compLogger.Enabled(zapcore.WarnLevel))
|
||||
require.False(t, compLogger.Enabled(zapcore.InfoLevel))
|
||||
})
|
||||
|
||||
t.Run("obeys component override", func(t *testing.T) {
|
||||
l, err := NewZapLogger(&Config{
|
||||
Level: "info",
|
||||
ComponentLevels: map[string]string{
|
||||
"sub": "debug",
|
||||
"sub2": "error",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
sub := zapLoggerCore(l.WithComponent("sub"))
|
||||
sub2 := zapLoggerCore(l.WithComponent("sub2"))
|
||||
require.True(t, sub.Enabled(zapcore.DebugLevel))
|
||||
require.False(t, sub2.Enabled(zapcore.InfoLevel))
|
||||
})
|
||||
|
||||
t.Run("updates dynamically", func(t *testing.T) {
|
||||
config := &Config{
|
||||
Level: "info",
|
||||
ComponentLevels: map[string]string{
|
||||
"sub": "debug",
|
||||
"sub2": "error",
|
||||
},
|
||||
}
|
||||
l, err := NewZapLogger(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
sub := zapLoggerCore(l.WithComponent("sub"))
|
||||
sub2 := zapLoggerCore(l.WithComponent("sub2.test"))
|
||||
err = config.Update(&Config{
|
||||
Level: "debug",
|
||||
ComponentLevels: map[string]string{
|
||||
"sub": "info",
|
||||
// sub2 removed
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, zapLoggerCore(l).Enabled(zapcore.DebugLevel))
|
||||
require.False(t, sub.Enabled(zapcore.DebugLevel))
|
||||
require.True(t, sub.Enabled(zapcore.InfoLevel))
|
||||
require.True(t, sub2.Enabled(zapcore.InfoLevel))
|
||||
})
|
||||
|
||||
t.Run("log output matches expected values", func(t *testing.T) {
|
||||
ws := &testBufferedWriteSyncer{}
|
||||
l, err := NewZapLogger(&Config{}, WithTap(zaputil.NewWriteEnabler(ws, zapcore.DebugLevel)))
|
||||
require.NoError(t, err)
|
||||
l.Debugw("foo", "bar", "baz")
|
||||
|
||||
var log struct {
|
||||
Level string
|
||||
TS float64
|
||||
Caller string
|
||||
Msg string
|
||||
Bar string
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(ws.Bytes(), &log))
|
||||
|
||||
require.Equal(t, "debug", log.Level)
|
||||
require.NotEqual(t, 0, log.TS)
|
||||
require.NotEqual(t, "", log.Caller)
|
||||
require.Equal(t, "foo", log.Msg)
|
||||
require.Equal(t, "baz", log.Bar)
|
||||
})
|
||||
|
||||
t.Run("component enabler for tapped logger returns lowest enabled level", func(t *testing.T) {
|
||||
tapLevel := zap.NewAtomicLevel()
|
||||
l, err := NewZapLogger(&Config{Level: "info"}, WithTap(zaputil.NewWriteEnabler(&testBufferedWriteSyncer{}, tapLevel)))
|
||||
require.NoError(t, err)
|
||||
|
||||
lvl := l.ComponentLeveler().ComponentLevel("foo")
|
||||
|
||||
// check config level
|
||||
require.False(t, lvl.Enabled(zapcore.DebugLevel))
|
||||
require.True(t, lvl.Enabled(zapcore.InfoLevel))
|
||||
|
||||
// check tap level
|
||||
tapLevel.SetLevel(zapcore.DebugLevel)
|
||||
require.True(t, lvl.Enabled(zapcore.DebugLevel))
|
||||
})
|
||||
}
|
||||
|
||||
type testBufferedWriteSyncer struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func (t *testBufferedWriteSyncer) Sync() error { return nil }
|
||||
95
logger/pionlogger/logadapter.go
Normal file
95
logger/pionlogger/logadapter.go
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pionlogger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/livekit/protocol/logger"
|
||||
)
|
||||
|
||||
// implements webrtc.LeveledLogger
|
||||
type logAdapter struct {
|
||||
logger logger.Logger
|
||||
ignoredPrefixes *prefixSet
|
||||
}
|
||||
|
||||
func (l *logAdapter) Trace(msg string) {
|
||||
l.logger.Debugw(msg)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Tracef(format string, args ...interface{}) {
|
||||
l.logger.Debugw(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *logAdapter) Debug(msg string) {
|
||||
if l.ignoredPrefixes.Match(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Debugw(msg)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Debugf(format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
if l.ignoredPrefixes.Match(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Debugw(msg)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Info(msg string) {
|
||||
if l.ignoredPrefixes.Match(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Infow(msg)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Infof(format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
if l.ignoredPrefixes.Match(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Infow(msg)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Warn(msg string) {
|
||||
if l.ignoredPrefixes.Match(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Warnw(msg, nil)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Warnf(format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
if l.ignoredPrefixes.Match(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Warnw(msg, nil)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Error(msg string) {
|
||||
if l.ignoredPrefixes.Match(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Errorw(msg, nil)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Errorf(format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
if l.ignoredPrefixes.Match(msg) {
|
||||
return
|
||||
}
|
||||
l.logger.Errorw(msg, nil)
|
||||
}
|
||||
103
logger/pionlogger/logger.go
Normal file
103
logger/pionlogger/logger.go
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pionlogger
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/pion/logging"
|
||||
|
||||
"github.com/livekit/protocol/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
pionIgnoredPrefixes = map[string]*prefixSet{
|
||||
"ice": {
|
||||
"pingAllCandidates called with no candidate pairs",
|
||||
"failed to send packet: io: read/write on closed pipe",
|
||||
"Ignoring remote candidate with tcpType active",
|
||||
"discard message from",
|
||||
"Failed to discover mDNS candidate",
|
||||
"Failed to read from candidate tcp",
|
||||
"remote mDNS candidate added, but mDNS is disabled",
|
||||
},
|
||||
"pc": {
|
||||
"Failed to accept RTCP stream is already closed",
|
||||
"Failed to accept RTP stream is already closed",
|
||||
"Incoming unhandled RTCP ssrc",
|
||||
},
|
||||
"tcp_mux": {
|
||||
"Error reading first packet from",
|
||||
"error closing connection",
|
||||
},
|
||||
"turn": {
|
||||
"error when handling datagram",
|
||||
"Failed to send ChannelData from allocation",
|
||||
"Failed to handle datagram",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type prefixSet []string
|
||||
|
||||
func (s *prefixSet) Match(msg string) bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, prefix := range *s {
|
||||
if strings.HasPrefix(msg, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func NewLoggerFactory(l logger.Logger) logging.LoggerFactory {
|
||||
if zl, ok := l.(logger.ZapLogger); ok {
|
||||
return &zapLoggerFactory{zl}
|
||||
}
|
||||
return &loggerFactory{l}
|
||||
}
|
||||
|
||||
// zapLoggerFactory implements logging.LoggerFactory interface for zap loggers
|
||||
type zapLoggerFactory struct {
|
||||
logger logger.ZapLogger
|
||||
}
|
||||
|
||||
func (f *zapLoggerFactory) NewLogger(scope string) logging.LeveledLogger {
|
||||
return &zapLogAdapter{
|
||||
logger: f.logger,
|
||||
level: f.logger.ComponentLeveler().ComponentLevel(formatComponent(scope)),
|
||||
scope: scope,
|
||||
ignoredPrefixes: pionIgnoredPrefixes[scope],
|
||||
}
|
||||
}
|
||||
|
||||
// loggerFactory implements logging.LoggerFactory interface for generic loggers
|
||||
type loggerFactory struct {
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func (f *loggerFactory) NewLogger(scope string) logging.LeveledLogger {
|
||||
return &logAdapter{
|
||||
logger: f.logger.WithComponent(formatComponent(scope)).WithCallDepth(1),
|
||||
ignoredPrefixes: pionIgnoredPrefixes[scope],
|
||||
}
|
||||
}
|
||||
|
||||
func formatComponent(scope string) string {
|
||||
return "pion." + scope
|
||||
}
|
||||
139
logger/pionlogger/zaplogadapter.go
Normal file
139
logger/pionlogger/zaplogadapter.go
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pionlogger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/livekit/protocol/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
zapLogAdapterStateCreated = iota
|
||||
zapLogAdapterStateBuilding
|
||||
zapLogAdapterStateReady
|
||||
)
|
||||
|
||||
// implements webrtc.LeveledLogger
|
||||
type zapLogAdapter struct {
|
||||
state atomic.Uint32
|
||||
logger logger.Logger
|
||||
level zapcore.LevelEnabler
|
||||
scope string
|
||||
ignoredPrefixes *prefixSet
|
||||
}
|
||||
|
||||
func (l *zapLogAdapter) init() {
|
||||
for l.state.Load() != zapLogAdapterStateReady {
|
||||
if l.state.CompareAndSwap(zapLogAdapterStateCreated, zapLogAdapterStateBuilding) {
|
||||
l.logger = l.logger.WithComponent(formatComponent(l.scope)).WithCallDepth(1)
|
||||
l.state.Store(zapLogAdapterStateReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *zapLogAdapter) Trace(msg string) {
|
||||
if l.level.Enabled(zap.DebugLevel) {
|
||||
l.init()
|
||||
l.logger.Debugw(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *zapLogAdapter) Tracef(format string, args ...interface{}) {
|
||||
if l.level.Enabled(zap.DebugLevel) {
|
||||
l.init()
|
||||
l.logger.Debugw(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *zapLogAdapter) Debug(msg string) {
|
||||
if l.level.Enabled(zap.DebugLevel) {
|
||||
if !l.ignoredPrefixes.Match(msg) {
|
||||
l.init()
|
||||
l.logger.Debugw(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *zapLogAdapter) Debugf(format string, args ...interface{}) {
|
||||
if l.level.Enabled(zap.DebugLevel) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
if !l.ignoredPrefixes.Match(msg) {
|
||||
l.init()
|
||||
l.logger.Debugw(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *zapLogAdapter) Info(msg string) {
|
||||
if l.level.Enabled(zap.InfoLevel) {
|
||||
if !l.ignoredPrefixes.Match(msg) {
|
||||
l.init()
|
||||
l.logger.Infow(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *zapLogAdapter) Infof(format string, args ...interface{}) {
|
||||
if l.level.Enabled(zap.InfoLevel) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
if !l.ignoredPrefixes.Match(msg) {
|
||||
l.init()
|
||||
l.logger.Infow(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *zapLogAdapter) Warn(msg string) {
|
||||
if l.level.Enabled(zap.WarnLevel) {
|
||||
if !l.ignoredPrefixes.Match(msg) {
|
||||
l.init()
|
||||
l.logger.Warnw(msg, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *zapLogAdapter) Warnf(format string, args ...interface{}) {
|
||||
if l.level.Enabled(zap.WarnLevel) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
if !l.ignoredPrefixes.Match(msg) {
|
||||
l.init()
|
||||
l.logger.Warnw(msg, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *zapLogAdapter) Error(msg string) {
|
||||
if l.level.Enabled(zap.ErrorLevel) {
|
||||
if !l.ignoredPrefixes.Match(msg) {
|
||||
l.init()
|
||||
l.logger.Errorw(msg, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *zapLogAdapter) Errorf(format string, args ...interface{}) {
|
||||
if l.level.Enabled(zap.ErrorLevel) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
if !l.ignoredPrefixes.Match(msg) {
|
||||
l.init()
|
||||
l.logger.Errorw(msg, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
173
logger/proto.go
Normal file
173
logger/proto.go
Normal file
@@ -0,0 +1,173 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"go.uber.org/zap/zapcore"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
)
|
||||
|
||||
func Proto(val proto.Message) zapcore.ObjectMarshaler {
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
return protoMarshaller{val.ProtoReflect()}
|
||||
}
|
||||
|
||||
var _ zapcore.ObjectMarshaler = protoMarshaller{}
|
||||
var _ zapcore.ObjectMarshaler = protoMapMarshaller{}
|
||||
var _ zapcore.ArrayMarshaler = protoListMarshaller{}
|
||||
|
||||
type protoMarshaller struct {
|
||||
m protoreflect.Message
|
||||
}
|
||||
|
||||
func (p protoMarshaller) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
fields := p.m.Descriptor().Fields()
|
||||
for i := 0; i < fields.Len(); i++ {
|
||||
f := fields.Get(i)
|
||||
k := f.JSONName()
|
||||
v := p.m.Get(f)
|
||||
|
||||
if f.IsMap() {
|
||||
if m := v.Map(); m.IsValid() {
|
||||
e.AddObject(k, protoMapMarshaller{f, m})
|
||||
}
|
||||
} else if f.IsList() {
|
||||
if m := v.List(); m.IsValid() {
|
||||
e.AddArray(k, protoListMarshaller{f, m})
|
||||
}
|
||||
} else {
|
||||
marshalProtoField(k, f, v, e)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type protoMapMarshaller struct {
|
||||
f protoreflect.FieldDescriptor
|
||||
m protoreflect.Map
|
||||
}
|
||||
|
||||
func (p protoMapMarshaller) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
p.m.Range(func(ki protoreflect.MapKey, vi protoreflect.Value) bool {
|
||||
var k string
|
||||
switch p.f.MapKey().Kind() {
|
||||
case protoreflect.BoolKind:
|
||||
k = strconv.FormatBool(ki.Bool())
|
||||
case protoreflect.Int32Kind, protoreflect.Int64Kind, protoreflect.Sint32Kind, protoreflect.Sint64Kind, protoreflect.Sfixed32Kind, protoreflect.Sfixed64Kind:
|
||||
k = strconv.FormatInt(ki.Int(), 10)
|
||||
case protoreflect.Uint32Kind, protoreflect.Uint64Kind, protoreflect.Fixed32Kind, protoreflect.Fixed64Kind:
|
||||
k = strconv.FormatUint(ki.Uint(), 10)
|
||||
case protoreflect.StringKind:
|
||||
k = ki.String()
|
||||
}
|
||||
marshalProtoField(k, p.f.MapValue(), vi, e)
|
||||
return true
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
type protoListMarshaller struct {
|
||||
f protoreflect.FieldDescriptor
|
||||
m protoreflect.List
|
||||
}
|
||||
|
||||
func (p protoListMarshaller) MarshalLogArray(e zapcore.ArrayEncoder) error {
|
||||
for i := 0; i < p.m.Len(); i++ {
|
||||
v := p.m.Get(i)
|
||||
switch p.f.Kind() {
|
||||
case protoreflect.BoolKind:
|
||||
e.AppendBool(v.Bool())
|
||||
case protoreflect.EnumKind:
|
||||
e.AppendInt32(int32(v.Enum()))
|
||||
case protoreflect.Int32Kind, protoreflect.Int64Kind, protoreflect.Sint32Kind, protoreflect.Sint64Kind, protoreflect.Sfixed32Kind, protoreflect.Sfixed64Kind:
|
||||
e.AppendInt64(v.Int())
|
||||
case protoreflect.Uint32Kind, protoreflect.Uint64Kind, protoreflect.Fixed32Kind, protoreflect.Fixed64Kind:
|
||||
e.AppendUint64(v.Uint())
|
||||
case protoreflect.FloatKind, protoreflect.DoubleKind:
|
||||
e.AppendFloat64(v.Float())
|
||||
case protoreflect.StringKind:
|
||||
e.AppendString(v.String())
|
||||
case protoreflect.BytesKind:
|
||||
e.AppendString(marshalProtoBytes(v.Bytes()))
|
||||
case protoreflect.MessageKind:
|
||||
e.AppendObject(protoMarshaller{v.Message()})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshalProtoField(k string, f protoreflect.FieldDescriptor, v protoreflect.Value, e zapcore.ObjectEncoder) {
|
||||
switch f.Kind() {
|
||||
case protoreflect.BoolKind:
|
||||
if b := v.Bool(); b {
|
||||
e.AddBool(k, b)
|
||||
}
|
||||
case protoreflect.EnumKind:
|
||||
if n := v.Enum(); n != 0 {
|
||||
e.AddInt32(k, int32(n))
|
||||
}
|
||||
case protoreflect.Int32Kind, protoreflect.Int64Kind, protoreflect.Sint32Kind, protoreflect.Sint64Kind, protoreflect.Sfixed32Kind, protoreflect.Sfixed64Kind:
|
||||
if n := v.Int(); n != 0 {
|
||||
e.AddInt64(k, n)
|
||||
}
|
||||
case protoreflect.Uint32Kind, protoreflect.Uint64Kind, protoreflect.Fixed32Kind, protoreflect.Fixed64Kind:
|
||||
if n := v.Uint(); n != 0 {
|
||||
e.AddUint64(k, n)
|
||||
}
|
||||
case protoreflect.FloatKind, protoreflect.DoubleKind:
|
||||
if n := v.Float(); n != 0 {
|
||||
e.AddFloat64(k, n)
|
||||
}
|
||||
case protoreflect.StringKind:
|
||||
if s := v.String(); s != "" {
|
||||
e.AddString(k, s)
|
||||
}
|
||||
case protoreflect.BytesKind:
|
||||
if b := v.Bytes(); len(b) != 0 {
|
||||
e.AddString(k, marshalProtoBytes(b))
|
||||
}
|
||||
case protoreflect.MessageKind:
|
||||
if m := v.Message(); m.IsValid() {
|
||||
e.AddObject(k, protoMarshaller{m})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func marshalProtoBytes(b []byte) string {
|
||||
n := len(b)
|
||||
if n > 64 {
|
||||
b = b[:64]
|
||||
}
|
||||
s := base64.RawStdEncoding.EncodeToString(b)
|
||||
switch {
|
||||
case n <= 64:
|
||||
return s
|
||||
case n < 1<<10:
|
||||
return fmt.Sprintf("%s... (%dbytes)", s, n)
|
||||
case n < 1<<20:
|
||||
return fmt.Sprintf("%s... (%.2fkB)", s, float64(n)/float64(1<<10))
|
||||
case n < 1<<30:
|
||||
return fmt.Sprintf("%s... (%.2fMB)", s, float64(n)/float64(1<<20))
|
||||
default:
|
||||
return fmt.Sprintf("%s... (%.2fGB)", s, float64(n)/float64(1<<30))
|
||||
}
|
||||
}
|
||||
102
logger/slog.go
Normal file
102
logger/slog.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"go.uber.org/zap/exp/zapslog"
|
||||
)
|
||||
|
||||
// NewSlogDiscard creates a slog.Handler that discards all logs.
|
||||
func NewSlogDiscard() slog.Handler {
|
||||
return slogDiscard{}
|
||||
}
|
||||
|
||||
// ToSlogHandler converts Logger to slog.Handler.
|
||||
func ToSlogHandler(log Logger) slog.Handler {
|
||||
switch log := log.(type) {
|
||||
case ZapLogger:
|
||||
zlog := log.ToZap().Desugar()
|
||||
return zapslog.NewHandler(zlog.Core(), &zapslog.HandlerOptions{AddSource: true})
|
||||
case LogRLogger:
|
||||
return logr.ToSlogHandler(log.toLogr())
|
||||
}
|
||||
return slogHandler{log, ""}
|
||||
}
|
||||
|
||||
type slogDiscard struct{}
|
||||
|
||||
func (_ slogDiscard) Enabled(ctx context.Context, level slog.Level) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (_ slogDiscard) Handle(ctx context.Context, record slog.Record) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l slogDiscard) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
return l
|
||||
}
|
||||
|
||||
func (l slogDiscard) WithGroup(name string) slog.Handler {
|
||||
return l
|
||||
}
|
||||
|
||||
type slogHandler struct {
|
||||
log Logger
|
||||
group string
|
||||
}
|
||||
|
||||
func (l slogHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||
return true // so such method on Logger
|
||||
}
|
||||
|
||||
func (l slogHandler) getGroup() string {
|
||||
group := l.group
|
||||
if group != "" {
|
||||
group = group + "."
|
||||
}
|
||||
return group
|
||||
}
|
||||
|
||||
func (l slogHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||
keysAndValues := make([]any, 0, r.NumAttrs()*2)
|
||||
group := l.getGroup()
|
||||
r.Attrs(func(attr slog.Attr) bool {
|
||||
keysAndValues = append(keysAndValues, group+attr.Key, attr.Value.Resolve().Any())
|
||||
return true
|
||||
})
|
||||
switch r.Level {
|
||||
case slog.LevelDebug:
|
||||
l.log.Debugw(r.Message, keysAndValues...)
|
||||
default:
|
||||
fallthrough
|
||||
case slog.LevelInfo:
|
||||
l.log.Infow(r.Message, keysAndValues...)
|
||||
case slog.LevelWarn:
|
||||
l.log.Warnw(r.Message, nil, keysAndValues...)
|
||||
case slog.LevelError:
|
||||
l.log.Errorw(r.Message, nil, keysAndValues...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
log := l.log
|
||||
keysAndValues := make([]any, 0, len(attrs)*2)
|
||||
group := l.getGroup()
|
||||
for _, attr := range attrs {
|
||||
keysAndValues = append(keysAndValues, group+attr.Key, attr.Value.Resolve().Any())
|
||||
}
|
||||
log = log.WithValues(keysAndValues...)
|
||||
return slogHandler{log, l.group}
|
||||
}
|
||||
|
||||
func (l slogHandler) WithGroup(name string) slog.Handler {
|
||||
group := name
|
||||
if l.group != "" {
|
||||
group = l.group + "." + name
|
||||
}
|
||||
return slogHandler{l.log, group}
|
||||
}
|
||||
123
logger/zaputil/deferrer.go
Normal file
123
logger/zaputil/deferrer.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package zaputil
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type deferredWrite struct {
|
||||
core zapcore.Core
|
||||
ent zapcore.Entry
|
||||
fields []zapcore.Field
|
||||
}
|
||||
|
||||
type Deferrer struct {
|
||||
mu sync.Mutex
|
||||
ready bool
|
||||
fields []zapcore.Field
|
||||
writes []*deferredWrite
|
||||
}
|
||||
|
||||
func (b *Deferrer) buffer(core zapcore.Core, ent zapcore.Entry, fields []zapcore.Field) bool {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if b.ready {
|
||||
return false
|
||||
}
|
||||
b.writes = append(b.writes, &deferredWrite{core, ent, fields})
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *Deferrer) flush() {
|
||||
b.mu.Lock()
|
||||
b.ready = true
|
||||
writes := b.writes
|
||||
b.writes = nil
|
||||
b.mu.Unlock()
|
||||
|
||||
var fields []zapcore.Field
|
||||
for _, w := range writes {
|
||||
fields = append(fields[:0], b.fields...)
|
||||
fields = append(fields, w.fields...)
|
||||
w.core.Write(w.ent, fields)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Deferrer) write(core zapcore.Core, ent zapcore.Entry, fields []zapcore.Field) error {
|
||||
if !b.buffer(core, ent, fields) {
|
||||
return core.Write(ent, append(fields[0:len(fields):len(fields)], b.fields...))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DeferredFieldResolver func(args ...any)
|
||||
|
||||
func NewDeferrer() (*Deferrer, DeferredFieldResolver) {
|
||||
buf := &Deferrer{}
|
||||
var resolveOnce sync.Once
|
||||
resolve := func(args ...any) {
|
||||
resolveOnce.Do(func() {
|
||||
fields := make([]zapcore.Field, 0, len(args))
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch arg := args[i].(type) {
|
||||
case zapcore.Field:
|
||||
fields = append(fields, arg)
|
||||
case string:
|
||||
if i < len(args)-1 {
|
||||
fields = append(fields, zap.Any(arg, args[i+1]))
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.fields = fields
|
||||
buf.flush()
|
||||
})
|
||||
}
|
||||
return buf, resolve
|
||||
}
|
||||
|
||||
type deferredValueCore struct {
|
||||
zapcore.Core
|
||||
def *Deferrer
|
||||
}
|
||||
|
||||
func NewDeferredValueCore(core zapcore.Core, def *Deferrer) zapcore.Core {
|
||||
def.mu.Lock()
|
||||
defer def.mu.Unlock()
|
||||
return &deferredValueCore{core, def}
|
||||
}
|
||||
|
||||
func (c *deferredValueCore) With(fields []zapcore.Field) zapcore.Core {
|
||||
return &deferredValueCore{
|
||||
Core: c.Core.With(fields),
|
||||
def: c.def,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *deferredValueCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||
if c.Enabled(ent.Level) {
|
||||
return ce.AddCore(ent, c)
|
||||
}
|
||||
return ce
|
||||
}
|
||||
|
||||
func (c *deferredValueCore) Write(ent zapcore.Entry, fields []zapcore.Field) error {
|
||||
return c.def.write(c.Core, ent, fields)
|
||||
}
|
||||
41
logger/zaputil/deferrer_test.go
Normal file
41
logger/zaputil/deferrer_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package zaputil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestDeferredLogger(t *testing.T) {
|
||||
c := &testCore{Core: zap.NewExample().Core()}
|
||||
d, resolve := NewDeferrer()
|
||||
dc := NewDeferredValueCore(c, d)
|
||||
s := zap.New(dc).Sugar()
|
||||
|
||||
s.Infow("test")
|
||||
require.Equal(t, 0, c.WriteCount())
|
||||
|
||||
s.With("a", "1").Infow("test")
|
||||
require.Equal(t, 0, c.WriteCount())
|
||||
|
||||
resolve("b", "2")
|
||||
require.Equal(t, 2, c.WriteCount())
|
||||
|
||||
s.With("c", "3").Infow("test")
|
||||
require.Equal(t, 3, c.WriteCount())
|
||||
}
|
||||
114
logger/zaputil/encoder.go
Normal file
114
logger/zaputil/encoder.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package zaputil
|
||||
|
||||
import (
|
||||
"go.uber.org/multierr"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type WriteEnabler struct {
|
||||
zapcore.WriteSyncer
|
||||
zapcore.LevelEnabler
|
||||
}
|
||||
|
||||
func NewWriteEnabler(ws zapcore.WriteSyncer, enab zapcore.LevelEnabler) *WriteEnabler {
|
||||
return &WriteEnabler{ws, enab}
|
||||
}
|
||||
|
||||
type discardWriteSyncer struct{}
|
||||
|
||||
func (discardWriteSyncer) Write(p []byte) (int, error) { return len(p), nil }
|
||||
func (discardWriteSyncer) Sync() error { return nil }
|
||||
|
||||
func NewDiscardWriteEnabler() *WriteEnabler {
|
||||
return NewWriteEnabler(discardWriteSyncer{}, zapcore.FatalLevel)
|
||||
}
|
||||
|
||||
func NewEncoderCore(enc zapcore.Encoder, out ...*WriteEnabler) zapcore.Core {
|
||||
return &encoderCore{
|
||||
enc: enc,
|
||||
out: out,
|
||||
}
|
||||
}
|
||||
|
||||
type encoderCore struct {
|
||||
enc zapcore.Encoder
|
||||
out []*WriteEnabler
|
||||
}
|
||||
|
||||
func (c encoderCore) Level() zapcore.Level {
|
||||
minLvl := zapcore.FatalLevel
|
||||
for _, out := range c.out {
|
||||
if lvl := zapcore.LevelOf(out); lvl < minLvl {
|
||||
minLvl = lvl
|
||||
}
|
||||
}
|
||||
return minLvl
|
||||
}
|
||||
|
||||
func (c encoderCore) Enabled(lvl zapcore.Level) bool {
|
||||
for _, out := range c.out {
|
||||
if out.Enabled(lvl) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *encoderCore) With(fields []zapcore.Field) zapcore.Core {
|
||||
dup := *c
|
||||
dup.enc = dup.enc.Clone()
|
||||
for _, f := range fields {
|
||||
f.AddTo(dup.enc)
|
||||
}
|
||||
return &dup
|
||||
}
|
||||
|
||||
func (c *encoderCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||
if c.Enabled(ent.Level) {
|
||||
return ce.AddCore(ent, c)
|
||||
}
|
||||
return ce
|
||||
}
|
||||
|
||||
func (c *encoderCore) Write(ent zapcore.Entry, fields []zapcore.Field) error {
|
||||
buf, err := c.enc.EncodeEntry(ent, fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, out := range c.out {
|
||||
if out.Enabled(ent.Level) {
|
||||
_, werr := out.Write(buf.Bytes())
|
||||
err = multierr.Append(err, werr)
|
||||
}
|
||||
}
|
||||
buf.Free()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ent.Level > zapcore.ErrorLevel {
|
||||
_ = c.Sync()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *encoderCore) Sync() error {
|
||||
var err error
|
||||
for _, out := range c.out {
|
||||
err = multierr.Append(err, out.Sync())
|
||||
}
|
||||
return err
|
||||
}
|
||||
151
logger/zaputil/fieldsampler.go
Normal file
151
logger/zaputil/fieldsampler.go
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package zaputil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/zeebo/xxh3"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type FieldSampleRate interface {
|
||||
Threshold() uint64
|
||||
}
|
||||
|
||||
func rateToThreshold(rate float64) uint64 {
|
||||
return math.MaxUint64 / 10000 * uint64(math.Max(math.Min(rate, 1), 0)*10000)
|
||||
}
|
||||
|
||||
type AtomicFieldSampleRate uint64
|
||||
|
||||
func NewAtomicFieldSampleRate(rate float64) *AtomicFieldSampleRate {
|
||||
var r AtomicFieldSampleRate
|
||||
r.SetRate(rate)
|
||||
return &r
|
||||
}
|
||||
|
||||
func (r *AtomicFieldSampleRate) SetRate(rate float64) {
|
||||
atomic.StoreUint64((*uint64)(r), rateToThreshold(rate))
|
||||
}
|
||||
|
||||
func (r *AtomicFieldSampleRate) Threshold() uint64 {
|
||||
return uint64(atomic.LoadUint64((*uint64)(r)))
|
||||
}
|
||||
|
||||
type FieldSamplerAction int
|
||||
|
||||
const (
|
||||
OmitSampledLog FieldSamplerAction = iota
|
||||
AnnotateSampledLog
|
||||
)
|
||||
|
||||
type FieldSamplerConfig struct {
|
||||
FieldName string
|
||||
Rate FieldSampleRate
|
||||
Action FieldSamplerAction
|
||||
AnnotationFieldName string
|
||||
}
|
||||
|
||||
func NewFieldSampler(core zapcore.Core, config FieldSamplerConfig) zapcore.Core {
|
||||
return &fieldSampler{
|
||||
Core: core,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
type fieldSampler struct {
|
||||
zapcore.Core
|
||||
config FieldSamplerConfig
|
||||
hash uint64
|
||||
}
|
||||
|
||||
var _ zapcore.Core = (*fieldSampler)(nil)
|
||||
|
||||
func (s *fieldSampler) With(fields []zapcore.Field) zapcore.Core {
|
||||
hash := s.hash
|
||||
if h, ok := s.hashSampleField(fields); ok {
|
||||
hash = h
|
||||
}
|
||||
|
||||
return &fieldSampler{
|
||||
Core: s.Core.With(fields),
|
||||
config: s.config,
|
||||
hash: hash,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *fieldSampler) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||
if s.Enabled(ent.Level) {
|
||||
return ce.AddCore(ent, s)
|
||||
}
|
||||
return ce
|
||||
}
|
||||
|
||||
func (s *fieldSampler) hashSampleField(fields []zapcore.Field) (uint64, bool) {
|
||||
v, ok := s.findSampleField(fields)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return xxh3.HashString(v), true
|
||||
}
|
||||
|
||||
func (s *fieldSampler) findSampleField(fields []zapcore.Field) (string, bool) {
|
||||
for _, f := range fields {
|
||||
if f.Key == s.config.FieldName {
|
||||
switch f.Type {
|
||||
case zapcore.StringType:
|
||||
return f.String, true
|
||||
case zapcore.ReflectType:
|
||||
rv := reflect.ValueOf(f.Interface)
|
||||
if rv.Kind() == reflect.String {
|
||||
return rv.String(), true
|
||||
}
|
||||
case zapcore.StringerType:
|
||||
if str, ok := f.Interface.(fmt.Stringer); ok {
|
||||
return str.String(), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (s *fieldSampler) test(fields []zapcore.Field) bool {
|
||||
if s.hash != 0 {
|
||||
return s.hash > s.config.Rate.Threshold()
|
||||
}
|
||||
if h, ok := s.hashSampleField(fields); ok {
|
||||
return h > s.config.Rate.Threshold()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *fieldSampler) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
||||
if s.test(fields) {
|
||||
switch s.config.Action {
|
||||
case OmitSampledLog:
|
||||
return nil
|
||||
case AnnotateSampledLog:
|
||||
fields = append(fields, zap.Bool(s.config.AnnotationFieldName, true))
|
||||
}
|
||||
}
|
||||
return s.Core.Write(entry, fields)
|
||||
}
|
||||
83
logger/zaputil/fieldsampler_test.go
Normal file
83
logger/zaputil/fieldsampler_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package zaputil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRoomSampleRate(t *testing.T) {
|
||||
expectedRate := 0.50
|
||||
n := 10000
|
||||
|
||||
c := &testCore{Core: zap.NewExample().Core()}
|
||||
s := zap.New(NewFieldSampler(c, FieldSamplerConfig{
|
||||
FieldName: "field",
|
||||
Rate: NewAtomicFieldSampleRate(expectedRate),
|
||||
})).Sugar()
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
s.Debugw("test", "field", fmt.Sprintf("field_%d", i))
|
||||
}
|
||||
|
||||
rate := float64(c.WriteCount()) / float64(n)
|
||||
require.Greater(t, 0.01, math.Abs(rate-expectedRate))
|
||||
}
|
||||
|
||||
func TestRoomSamplerWith(t *testing.T) {
|
||||
c := &testCore{Core: zap.NewExample().Core()}
|
||||
s := zap.New(NewFieldSampler(c, FieldSamplerConfig{
|
||||
FieldName: "field",
|
||||
Rate: NewAtomicFieldSampleRate(0),
|
||||
}).(*fieldSampler)).Sugar()
|
||||
|
||||
s.Debugw("test", "field", "test")
|
||||
require.Equal(t, 0, c.WriteCount())
|
||||
|
||||
s.With("field", "test").Debugw("test")
|
||||
require.Equal(t, 0, c.WriteCount())
|
||||
}
|
||||
|
||||
func TestRoomSampleFindSampleField(t *testing.T) {
|
||||
s := NewFieldSampler(nil, FieldSamplerConfig{
|
||||
FieldName: "field",
|
||||
Rate: NewAtomicFieldSampleRate(1),
|
||||
}).(*fieldSampler)
|
||||
|
||||
v, ok := s.findSampleField([]zapcore.Field{zap.String("field", "test")})
|
||||
require.Equal(t, "test", v)
|
||||
require.True(t, ok)
|
||||
|
||||
v, ok = s.findSampleField([]zapcore.Field{zap.Reflect("field", testStringLike("test"))})
|
||||
require.Equal(t, "test", v)
|
||||
require.True(t, ok)
|
||||
|
||||
v, ok = s.findSampleField([]zapcore.Field{zap.Stringer("field", testStringer("test"))})
|
||||
require.Equal(t, "test", v)
|
||||
require.True(t, ok)
|
||||
|
||||
_, ok = s.findSampleField([]zapcore.Field{zap.Int("field", 123)})
|
||||
require.False(t, ok)
|
||||
|
||||
_, ok = s.findSampleField([]zapcore.Field{})
|
||||
require.False(t, ok)
|
||||
}
|
||||
23
logger/zaputil/orlevelenabler.go
Normal file
23
logger/zaputil/orlevelenabler.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package zaputil
|
||||
|
||||
import "go.uber.org/zap/zapcore"
|
||||
|
||||
type OrLevelEnabler [2]zapcore.LevelEnabler
|
||||
|
||||
func (e OrLevelEnabler) Enabled(lvl zapcore.Level) bool {
|
||||
return e[0].Enabled(lvl) || e[1].Enabled(lvl)
|
||||
}
|
||||
130
logger/zaputil/sampler.go
Normal file
130
logger/zaputil/sampler.go
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright (c) 2016-2022 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package zaputil
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/zeebo/xxh3"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// this is lifted almost entirely from zap but with one significant change -
|
||||
// the state is injected into the sampler core to allow rebuilding the logger
|
||||
// without resetting the counts.
|
||||
|
||||
const (
|
||||
minLevel = zapcore.DebugLevel
|
||||
maxLevel = zapcore.FatalLevel
|
||||
|
||||
numLevels = maxLevel - minLevel + 1
|
||||
countersPerLevel = 4096
|
||||
)
|
||||
|
||||
type counter struct {
|
||||
resetAt atomic.Int64
|
||||
counter atomic.Uint64
|
||||
}
|
||||
|
||||
type counters [numLevels][countersPerLevel]counter
|
||||
|
||||
func newCounters() *counters {
|
||||
return &counters{}
|
||||
}
|
||||
|
||||
func (cs *counters) get(lvl zapcore.Level, key string) *counter {
|
||||
i := lvl - minLevel
|
||||
j := xxh3.HashString(key) % countersPerLevel
|
||||
return &cs[i][j]
|
||||
}
|
||||
|
||||
func (c *counter) IncCheckReset(t time.Time, tick time.Duration) uint64 {
|
||||
tn := t.UnixNano()
|
||||
resetAfter := c.resetAt.Load()
|
||||
if resetAfter > tn {
|
||||
return c.counter.Add(1)
|
||||
}
|
||||
|
||||
c.counter.Store(1)
|
||||
|
||||
newResetAfter := tn + tick.Nanoseconds()
|
||||
if !c.resetAt.CompareAndSwap(resetAfter, newResetAfter) {
|
||||
// We raced with another goroutine trying to reset, and it also reset
|
||||
// the counter to 1, so we need to reincrement the counter.
|
||||
return c.counter.Add(1)
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
type Sampler struct {
|
||||
counts *counters
|
||||
tick time.Duration
|
||||
first, thereafter uint64
|
||||
}
|
||||
|
||||
func NewSampler(tick time.Duration, first, thereafter int) *Sampler {
|
||||
return &Sampler{
|
||||
tick: tick,
|
||||
counts: newCounters(),
|
||||
first: uint64(first),
|
||||
thereafter: uint64(thereafter),
|
||||
}
|
||||
}
|
||||
|
||||
func NewSamplerCore(core zapcore.Core, s *Sampler) zapcore.Core {
|
||||
return &sampler{
|
||||
Core: core,
|
||||
s: s,
|
||||
}
|
||||
}
|
||||
|
||||
type sampler struct {
|
||||
zapcore.Core
|
||||
s *Sampler
|
||||
}
|
||||
|
||||
func (s *sampler) Level() zapcore.Level {
|
||||
return zapcore.LevelOf(s.Core)
|
||||
}
|
||||
|
||||
func (s *sampler) With(fields []zapcore.Field) zapcore.Core {
|
||||
return &sampler{
|
||||
Core: s.Core.With(fields),
|
||||
s: s.s,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sampler) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||
if !s.Enabled(ent.Level) {
|
||||
return ce
|
||||
}
|
||||
|
||||
if ent.Level >= minLevel && ent.Level <= maxLevel {
|
||||
counter := s.s.counts.get(ent.Level, ent.Message)
|
||||
n := counter.IncCheckReset(ent.Time, s.s.tick)
|
||||
if n > s.s.first && (s.s.thereafter == 0 || (n-s.s.first)%s.s.thereafter != 0) {
|
||||
return ce
|
||||
}
|
||||
}
|
||||
return s.Core.Check(ent, ce)
|
||||
}
|
||||
76
logger/zaputil/zaputil.go
Normal file
76
logger/zaputil/zaputil.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package zaputil
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
func encoderWithValues(enc zapcore.Encoder, kvs ...any) zapcore.Encoder {
|
||||
clone := enc.Clone()
|
||||
for i := 1; i < len(kvs); i += 2 {
|
||||
if key, ok := kvs[i-1].(string); ok {
|
||||
zap.Any(key, kvs[i]).AddTo(clone)
|
||||
}
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
type Encoder[T any] interface {
|
||||
WithValues(kvs ...any) T
|
||||
Core(console, json *WriteEnabler) zapcore.Core
|
||||
}
|
||||
|
||||
type DevelopmentEncoder struct {
|
||||
console zapcore.Encoder
|
||||
json zapcore.Encoder
|
||||
}
|
||||
|
||||
func NewDevelopmentEncoder() DevelopmentEncoder {
|
||||
return DevelopmentEncoder{
|
||||
console: zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
|
||||
json: zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
|
||||
}
|
||||
}
|
||||
|
||||
func (e DevelopmentEncoder) WithValues(kvs ...any) DevelopmentEncoder {
|
||||
e.console = encoderWithValues(e.console, kvs...)
|
||||
e.json = encoderWithValues(e.json, kvs...)
|
||||
return e
|
||||
}
|
||||
|
||||
func (e DevelopmentEncoder) Core(console, json *WriteEnabler) zapcore.Core {
|
||||
return zapcore.NewTee(NewEncoderCore(e.console, console), NewEncoderCore(e.json, json))
|
||||
}
|
||||
|
||||
type ProductionEncoder struct {
|
||||
json zapcore.Encoder
|
||||
}
|
||||
|
||||
func NewProductionEncoder() ProductionEncoder {
|
||||
return ProductionEncoder{
|
||||
json: zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
|
||||
}
|
||||
}
|
||||
|
||||
func (e ProductionEncoder) WithValues(kvs ...any) ProductionEncoder {
|
||||
e.json = encoderWithValues(e.json, kvs...)
|
||||
return e
|
||||
}
|
||||
|
||||
func (e ProductionEncoder) Core(console, json *WriteEnabler) zapcore.Core {
|
||||
return NewEncoderCore(e.json, console, json)
|
||||
}
|
||||
58
logger/zaputil/zaputil_test.go
Normal file
58
logger/zaputil/zaputil_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package zaputil
|
||||
|
||||
import (
|
||||
"go.uber.org/atomic"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type testStringer string
|
||||
|
||||
func (s testStringer) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
type testStringLike string
|
||||
|
||||
type testCore struct {
|
||||
zapcore.Core
|
||||
writeCount *atomic.Int32
|
||||
}
|
||||
|
||||
func (c *testCore) init() {
|
||||
if c.writeCount == nil {
|
||||
c.writeCount = &atomic.Int32{}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *testCore) WriteCount() int {
|
||||
c.init()
|
||||
return int(c.writeCount.Load())
|
||||
}
|
||||
|
||||
func (c *testCore) With(fields []zapcore.Field) zapcore.Core {
|
||||
c.init()
|
||||
return &testCore{
|
||||
Core: c.Core.With(fields),
|
||||
writeCount: c.writeCount,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *testCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
||||
s.init()
|
||||
s.writeCount.Inc()
|
||||
return nil
|
||||
}
|
||||
204
magefile.go
Normal file
204
magefile.go
Normal file
@@ -0,0 +1,204 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build mage
|
||||
// +build mage
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/livekit/mageutil"
|
||||
"github.com/livekit/protocol/psrpc"
|
||||
)
|
||||
|
||||
var Default = Proto
|
||||
|
||||
func Bootstrap() error {
|
||||
return mageutil.Run(context.Background(),
|
||||
"go install github.com/twitchtv/twirp/protoc-gen-twirp@latest",
|
||||
"go install google.golang.org/protobuf/cmd/protoc-gen-go@latest",
|
||||
"go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest",
|
||||
"go install github.com/livekit/psrpc/protoc-gen-psrpc@latest",
|
||||
)
|
||||
}
|
||||
|
||||
// regenerate protobuf
|
||||
func Proto() error {
|
||||
twirpProtoFiles := []string{
|
||||
"livekit_egress.proto",
|
||||
"livekit_ingress.proto",
|
||||
"livekit_room.proto",
|
||||
"livekit_sip.proto",
|
||||
}
|
||||
protoFiles := []string{
|
||||
"livekit_agent.proto",
|
||||
"livekit_analytics.proto",
|
||||
"livekit_internal.proto",
|
||||
"livekit_models.proto",
|
||||
"livekit_rtc.proto",
|
||||
"livekit_webhook.proto",
|
||||
}
|
||||
grpcProtoFiles := []string{
|
||||
"infra/link.proto",
|
||||
"rpc/analytics.proto",
|
||||
}
|
||||
psrpcProtoFiles := []string{
|
||||
"rpc/agent.proto",
|
||||
"rpc/egress.proto",
|
||||
"rpc/ingress.proto",
|
||||
"rpc/io.proto",
|
||||
"rpc/keepalive.proto",
|
||||
"rpc/participant.proto",
|
||||
"rpc/room.proto",
|
||||
"rpc/signal.proto",
|
||||
"rpc/sip.proto",
|
||||
}
|
||||
|
||||
fmt.Println("generating protobuf")
|
||||
target := "livekit"
|
||||
if err := os.MkdirAll(target, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
protoc, err := getToolPath("protoc")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protocGoPath, err := getToolPath("protoc-gen-go")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
twirpPath, err := getToolPath("protoc-gen-twirp")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protocGrpcGoPath, err := getToolPath("protoc-gen-go-grpc")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("generating twirp protobuf")
|
||||
args := append([]string{
|
||||
"--go_out", target,
|
||||
"--twirp_out", target,
|
||||
"--go_opt=paths=source_relative",
|
||||
"--twirp_opt=paths=source_relative",
|
||||
"--plugin=go=" + protocGoPath,
|
||||
"--plugin=twirp=" + twirpPath,
|
||||
"-I=./protobufs",
|
||||
}, twirpProtoFiles...)
|
||||
cmd := exec.Command(protoc, args...)
|
||||
connectStd(cmd)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("generating protobuf")
|
||||
args = append([]string{
|
||||
"--go_out", target,
|
||||
"--go-grpc_out", target,
|
||||
"--go_opt=paths=source_relative",
|
||||
"--go-grpc_opt=paths=source_relative",
|
||||
"--plugin=go=" + protocGoPath,
|
||||
"--plugin=go-grpc=" + protocGrpcGoPath,
|
||||
"-I=./protobufs",
|
||||
}, protoFiles...)
|
||||
cmd = exec.Command(protoc, args...)
|
||||
connectStd(cmd)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("generating grpc protobuf")
|
||||
args = append([]string{
|
||||
"--go_out", ".",
|
||||
"--go-grpc_out", ".",
|
||||
"--go_opt=paths=source_relative",
|
||||
"--go-grpc_opt=paths=source_relative",
|
||||
"--plugin=go=" + protocGoPath,
|
||||
"--plugin=go-grpc=" + protocGrpcGoPath,
|
||||
"-I=./protobufs",
|
||||
}, grpcProtoFiles...)
|
||||
cmd = exec.Command(protoc, args...)
|
||||
connectStd(cmd)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("generating psrpc protobuf")
|
||||
|
||||
psrpcDir, err := mageutil.GetPkgDir("github.com/livekit/psrpc")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
psrpcPath, err := mageutil.GetToolPath("protoc-gen-psrpc")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := psrpc.CheckCompilerVersion(psrpcPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args = append([]string{
|
||||
"--go_out", ".",
|
||||
"--psrpc_out", ".",
|
||||
"--go_opt=paths=source_relative",
|
||||
"--psrpc_opt=paths=source_relative",
|
||||
"--plugin=go=" + protocGoPath,
|
||||
"--plugin=psrpc=" + psrpcPath,
|
||||
"-I" + psrpcDir + "/protoc-gen-psrpc/options",
|
||||
"-I=./protobufs",
|
||||
}, psrpcProtoFiles...)
|
||||
cmd = exec.Command(protoc, args...)
|
||||
mageutil.ConnectStd(cmd)
|
||||
if err = cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// run tests
|
||||
func Test() error {
|
||||
cmd := exec.Command("go", "test", "-race", "./...")
|
||||
connectStd(cmd)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
func getToolPath(name string) (string, error) {
|
||||
if p, err := exec.LookPath(name); err == nil {
|
||||
return p, nil
|
||||
}
|
||||
// check under gopath
|
||||
gopath := os.Getenv("GOPATH")
|
||||
if gopath == "" {
|
||||
gopath = build.Default.GOPATH
|
||||
}
|
||||
p := filepath.Join(gopath, "bin", name)
|
||||
if _, err := os.Stat(p); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func connectStd(cmd *exec.Cmd) {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
51
model.proto
51
model.proto
@@ -1,51 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package livekit;
|
||||
option go_package = "github.com/livekit/livekit-server/proto/livekit";
|
||||
|
||||
message Room {
|
||||
string sid = 1;
|
||||
string name = 2;
|
||||
uint32 empty_timeout = 3;
|
||||
uint32 max_participants = 4;
|
||||
int64 creation_time = 5;
|
||||
string turn_password = 6;
|
||||
}
|
||||
|
||||
message ParticipantInfo {
|
||||
enum State {
|
||||
// websocket' connected, but not offered yet
|
||||
JOINING = 0;
|
||||
// server received client offer
|
||||
JOINED = 1;
|
||||
// ICE connectivity established
|
||||
ACTIVE = 2;
|
||||
// WS disconnected
|
||||
DISCONNECTED = 3;
|
||||
}
|
||||
string sid = 1;
|
||||
string identity = 2;
|
||||
State state = 3;
|
||||
repeated TrackInfo tracks = 4;
|
||||
string metadata = 5;
|
||||
}
|
||||
|
||||
enum TrackType {
|
||||
AUDIO = 0;
|
||||
VIDEO = 1;
|
||||
DATA = 2;
|
||||
}
|
||||
|
||||
message TrackInfo {
|
||||
string sid = 1;
|
||||
TrackType type = 2;
|
||||
string name = 3;
|
||||
bool muted = 4;
|
||||
}
|
||||
|
||||
message DataMessage {
|
||||
oneof value {
|
||||
string text = 1;
|
||||
bytes binary = 2;
|
||||
}
|
||||
}
|
||||
13
package.json
Normal file
13
package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "github.com/livekit/protocol",
|
||||
"private": true,
|
||||
"version": "1.19.1",
|
||||
"scripts": {
|
||||
"changeset": "changeset",
|
||||
"ci:publish": "pnpm --filter @livekit/protocol run build && changeset publish"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.27.1",
|
||||
"@livekit/changesets-changelog-github": "^0.0.4"
|
||||
}
|
||||
}
|
||||
3
packages/javascript/.gitignore
vendored
Normal file
3
packages/javascript/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
src/gen
|
||||
!src/gen/version.d.ts
|
||||
111
packages/javascript/CHANGELOG.md
Normal file
111
packages/javascript/CHANGELOG.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# @livekit/protocol
|
||||
|
||||
## 1.19.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Add ErrorResponse - [#743](https://github.com/livekit/protocol/pull/743) ([@lukasIO](https://github.com/lukasIO))
|
||||
|
||||
## 1.19.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add SIP grants. - [#745](https://github.com/livekit/protocol/pull/745) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
## 1.18.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Split SIP Trunk configuration to inbound and outbound parts. - [#738](https://github.com/livekit/protocol/pull/738) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
- add Participant attributes key/val storage - [#733](https://github.com/livekit/protocol/pull/733) ([@davidzhao](https://github.com/davidzhao))
|
||||
|
||||
## 1.17.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add support for additional SIP transports. - [#719](https://github.com/livekit/protocol/pull/719) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
- Add ICERestartWHIPResource to RPC - [#716](https://github.com/livekit/protocol/pull/716) ([@Sean-Der](https://github.com/Sean-Der))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Added error code field to EgressInfo - [#714](https://github.com/livekit/protocol/pull/714) ([@frostbyte73](https://github.com/frostbyte73))
|
||||
|
||||
- Include node_id with analytics events - [#727](https://github.com/livekit/protocol/pull/727) ([@davidzhao](https://github.com/davidzhao))
|
||||
|
||||
- Add notice to use participant identity from DataPacket for transcriptions. - [#706](https://github.com/livekit/protocol/pull/706) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
## 1.16.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- - Add RedactAutoEncodedOutput to support redacting auto egress types - [#712](https://github.com/livekit/protocol/pull/712) ([@biglittlebigben](https://github.com/biglittlebigben))
|
||||
- Redact image outputs
|
||||
- Support AliOSS uploads for auto egress
|
||||
|
||||
- Allow inbound number filtering on SIP Dispatch Rules - [#707](https://github.com/livekit/protocol/pull/707) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
- Move egress request redacting routines to protocol - [#711](https://github.com/livekit/protocol/pull/711) ([@biglittlebigben](https://github.com/biglittlebigben))
|
||||
|
||||
## 1.15.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add metadata to SIP trunks, dispatch rules and participants. Change SIP participant identity prefix to `sip_`. - [#696](https://github.com/livekit/protocol/pull/696) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
- Move language into TranscriptionSegment #703 - [#704](https://github.com/livekit/protocol/pull/704) ([@lukasIO](https://github.com/lukasIO))
|
||||
|
||||
## 1.14.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Added real-time Transcription protocol - [#686](https://github.com/livekit/protocol/pull/686) ([@davidzhao](https://github.com/davidzhao))
|
||||
|
||||
- WHIP protocol change - [#680](https://github.com/livekit/protocol/pull/680) ([@biglittlebigben](https://github.com/biglittlebigben))
|
||||
|
||||
Deprecate the bypass_transcoding property in all ingress APIs and introduce the optional enable_transcoding property. This property will default to false for WHIP and to true for all other ingress types.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Add SIP participant name. - [#687](https://github.com/livekit/protocol/pull/687) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
## 1.13.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add and option to play ringtone for SIP outbound calls. - [#671](https://github.com/livekit/protocol/pull/671) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
## 1.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fixed bounds check when masking short SIP numbers. - [#650](https://github.com/livekit/protocol/pull/650) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
- Add signal requests for local track updates - [#651](https://github.com/livekit/protocol/pull/651) ([@lukasIO](https://github.com/lukasIO))
|
||||
|
||||
- Allow sending DTMF when creating SIP participant. - [#658](https://github.com/livekit/protocol/pull/658) ([@dennwc](https://github.com/dennwc))
|
||||
|
||||
## 1.11.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Align package version with manually tagged golang module - [#646](https://github.com/livekit/protocol/pull/646) ([@lukasIO](https://github.com/lukasIO))
|
||||
|
||||
## 1.10.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fix granular export paths - [#642](https://github.com/livekit/protocol/pull/642) ([@lukasIO](https://github.com/lukasIO))
|
||||
|
||||
## 1.10.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Export room stubs from package - [#640](https://github.com/livekit/protocol/pull/640) ([@lukasIO](https://github.com/lukasIO))
|
||||
|
||||
## 1.10.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fix import paths - [#635](https://github.com/livekit/protocol/pull/635) ([@lukasIO](https://github.com/lukasIO))
|
||||
36
packages/javascript/package.json
Normal file
36
packages/javascript/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@livekit/protocol",
|
||||
"version": "1.19.1",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"main": "src/index.js",
|
||||
"types": "src/index.d.ts",
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.d.ts",
|
||||
"default": "./src/index.js"
|
||||
},
|
||||
"./*": {
|
||||
"types": "./src/gen/livekit_*_pb.d.ts",
|
||||
"default": "./src/gen/livekit_*_pb.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"generate:version": "genversion --esm --semi src/gen/version.js",
|
||||
"generate:proto": "protoc --es_out src/gen --es_opt target=js+dts -I=../../protobufs ../../protobufs/livekit_room.proto ../../protobufs/livekit_rtc.proto ../../protobufs/livekit_models.proto ../../protobufs/livekit_agent.proto ../../protobufs/livekit_webhook.proto ../../protobufs/livekit_egress.proto ../../protobufs/livekit_ingress.proto ../../protobufs/livekit_sip.proto",
|
||||
"build": "pnpm generate:version && pnpm generate:proto"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "LiveKit",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@bufbuild/protoc-gen-es": "^1.7.2",
|
||||
"genversion": "^3.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^1.7.2"
|
||||
}
|
||||
}
|
||||
1
packages/javascript/src/gen/version.d.ts
vendored
Normal file
1
packages/javascript/src/gen/version.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare const version: string;
|
||||
10
packages/javascript/src/index.d.ts
vendored
Normal file
10
packages/javascript/src/index.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
export * from "./gen/version.js";
|
||||
export * from "./gen/livekit_room_pb.js";
|
||||
export * from "./gen/livekit_agent_pb.js";
|
||||
export * from "./gen/livekit_models_pb.js";
|
||||
export * from "./gen/livekit_rtc_pb.js";
|
||||
export * from "./gen/livekit_webhook_pb.js";
|
||||
export * from "./gen/livekit_egress_pb.js";
|
||||
export * from "./gen/livekit_ingress_pb.js";
|
||||
export * from "./gen/livekit_sip_pb.js";
|
||||
export { protoInt64 } from "@bufbuild/protobuf";
|
||||
11
packages/javascript/src/index.js
Normal file
11
packages/javascript/src/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
// @ts-check
|
||||
export * from "./gen/version.js";
|
||||
export * from "./gen/livekit_room_pb.js";
|
||||
export * from "./gen/livekit_agent_pb.js";
|
||||
export * from "./gen/livekit_models_pb.js";
|
||||
export * from "./gen/livekit_rtc_pb.js";
|
||||
export * from "./gen/livekit_webhook_pb.js";
|
||||
export * from "./gen/livekit_egress_pb.js";
|
||||
export * from "./gen/livekit_ingress_pb.js";
|
||||
export * from "./gen/livekit_sip_pb.js";
|
||||
export { protoInt64 } from "@bufbuild/protobuf";
|
||||
1269
pnpm-lock.yaml
generated
Normal file
1269
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
pnpm-workspace.yaml
Normal file
3
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
packages:
|
||||
- "packages/javascript"
|
||||
- "."
|
||||
83
pprof/pprof.go
Normal file
83
pprof/pprof.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pprof
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"runtime/pprof"
|
||||
"time"
|
||||
|
||||
"github.com/livekit/psrpc"
|
||||
)
|
||||
|
||||
const (
|
||||
cpuProfileName = "cpu"
|
||||
defaultTimeout = 30
|
||||
)
|
||||
|
||||
var (
|
||||
ErrProfileNotFound = psrpc.NewErrorf(psrpc.NotFound, "profile not found")
|
||||
)
|
||||
|
||||
func GetProfileData(ctx context.Context, profileName string, timeout int, debug int) (b []byte, err error) {
|
||||
switch profileName {
|
||||
case cpuProfileName:
|
||||
return GetCpuProfileData(ctx, timeout)
|
||||
default:
|
||||
return GetGenericProfileData(profileName, debug)
|
||||
}
|
||||
}
|
||||
|
||||
func GetCpuProfileData(ctx context.Context, timeout int) (b []byte, err error) {
|
||||
if timeout == 0 {
|
||||
timeout = defaultTimeout
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err = pprof.StartCPUProfile(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// finish async in order not to block, since we will not use the results
|
||||
go pprof.StopCPUProfile()
|
||||
return nil, context.Canceled
|
||||
case <-time.After(time.Duration(timeout) * time.Second):
|
||||
// break
|
||||
}
|
||||
|
||||
pprof.StopCPUProfile()
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func GetGenericProfileData(profileName string, debug int) (b []byte, err error) {
|
||||
pp := pprof.Lookup(profileName)
|
||||
if pp == nil {
|
||||
return nil, ErrProfileNotFound
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
err = pp.WriteTo(buf, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
50
protobufs/infra/link.proto
Normal file
50
protobufs/infra/link.proto
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package rpc;
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
option go_package = "github.com/livekit/protocol/infra";
|
||||
|
||||
service Link {
|
||||
rpc WatchLocalLinks(WatchLocalLinksRequest) returns (stream WatchLocalLinksResponse);
|
||||
rpc SimulateLinkState(SimulateLinkStateRequest) returns (SimulateLinkStateResponse);
|
||||
}
|
||||
|
||||
message WatchLocalLinksRequest {}
|
||||
|
||||
message WatchLocalLinksResponse {
|
||||
string local_region = 1;
|
||||
string remote_region = 2;
|
||||
int64 rtt = 3;
|
||||
int64 jitter = 4;
|
||||
double packet_loss = 5;
|
||||
bool disabled = 6;
|
||||
google.protobuf.Timestamp last_read = 7;
|
||||
}
|
||||
|
||||
message SimulateLinkStateRequest {
|
||||
string local_region = 1;
|
||||
string remote_region = 2;
|
||||
optional int64 rtt = 3;
|
||||
optional int64 jitter = 4;
|
||||
optional double packet_loss = 5;
|
||||
optional bool disabled = 6;
|
||||
int64 timeout = 7;
|
||||
}
|
||||
|
||||
message SimulateLinkStateResponse {}
|
||||
218
protobufs/livekit_agent.proto
Normal file
218
protobufs/livekit_agent.proto
Normal file
@@ -0,0 +1,218 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package livekit;
|
||||
option go_package = "github.com/livekit/protocol/livekit";
|
||||
option csharp_namespace = "LiveKit.Proto";
|
||||
option ruby_package = "LiveKit::Proto";
|
||||
|
||||
import "livekit_models.proto";
|
||||
|
||||
service AgentService {
|
||||
rpc CreateAgentJobDefinition(CreateAgentJobDefinitionRequest) returns (JobDefinition);
|
||||
rpc DeleteAgentJobDefinition(DeleteAgentJobDefinitionRequest) returns (JobDefinition);
|
||||
rpc ListAgentJobDefinitions(ListAgentJobDefinitionsRequesst) returns (ListAgentJobDefinitionsResponse);
|
||||
}
|
||||
|
||||
message CreateAgentJobDefinitionRequest {
|
||||
JobType type = 1;
|
||||
string room = 2;
|
||||
repeated string participant_identity = 3;
|
||||
string namespace = 4;
|
||||
string metadata = 5;
|
||||
}
|
||||
|
||||
message DeleteAgentJobDefinitionRequest {
|
||||
string job_definition_id = 1;
|
||||
}
|
||||
|
||||
message ListAgentJobDefinitionsRequesst {
|
||||
string job_definition_id = 1; // if set, only the job definition whose id is given will be returned
|
||||
string room = 2; // name of the room to list agents for. Must be set.
|
||||
}
|
||||
|
||||
message ListAgentJobDefinitionsResponse {
|
||||
repeated JobDefinition job_definitions = 1;
|
||||
}
|
||||
|
||||
message JobDefinition {
|
||||
string id = 1;
|
||||
JobType type = 2;
|
||||
string room = 3;
|
||||
repeated string participant_identity = 4;
|
||||
string namespace = 5;
|
||||
string metadata = 6;
|
||||
JobDefinitionState state = 7;
|
||||
}
|
||||
|
||||
message JobDefinitionState {
|
||||
// For job definitions of tyoe JT_ROOM, there will be at most 1 job.
|
||||
// For job definitions of type JT_PUBLISHER, there will be 1 job per provided participant identity, or per publisher in the room if left empty.
|
||||
repeated Job jobs = 1;
|
||||
int64 created_at = 2;
|
||||
int64 deleted_at = 3;
|
||||
}
|
||||
|
||||
message Job {
|
||||
string id = 1;
|
||||
JobType type = 2;
|
||||
Room room = 3;
|
||||
optional ParticipantInfo participant = 4;
|
||||
string namespace = 5;
|
||||
string metadata = 6;
|
||||
JobState state = 7;
|
||||
}
|
||||
|
||||
message JobState {
|
||||
JobStatus status = 1;
|
||||
string error = 2;
|
||||
int64 started_at = 3;
|
||||
int64 ended_at = 4;
|
||||
int64 updated_at = 5;
|
||||
}
|
||||
|
||||
// from Worker to Server
|
||||
message WorkerMessage {
|
||||
oneof message {
|
||||
// agent workers need to register themselves with the server first
|
||||
RegisterWorkerRequest register = 1;
|
||||
// worker confirms to server that it's available for a job, or declines it
|
||||
AvailabilityResponse availability = 2;
|
||||
|
||||
// worker can update its status to the server, including taking itself out of the pool
|
||||
UpdateWorkerStatus update_worker = 3;
|
||||
// job can send status updates to the server, useful for tracking progress
|
||||
UpdateJobStatus update_job = 4;
|
||||
|
||||
WorkerPing ping = 5;
|
||||
SimulateJobRequest simulate_job = 6;
|
||||
MigrateJobRequest migrate_job = 7;
|
||||
};
|
||||
}
|
||||
|
||||
// from Server to Worker
|
||||
message ServerMessage {
|
||||
oneof message {
|
||||
// server confirms the registration, from this moment on, the worker is considered active
|
||||
RegisterWorkerResponse register = 1;
|
||||
// server asks worker to confirm availability for a job
|
||||
AvailabilityRequest availability = 2;
|
||||
JobAssignment assignment = 3;
|
||||
JobTermination termination = 5;
|
||||
WorkerPong pong = 4;
|
||||
}
|
||||
}
|
||||
|
||||
enum JobType {
|
||||
JT_ROOM = 0;
|
||||
JT_PUBLISHER = 1;
|
||||
}
|
||||
|
||||
enum WorkerStatus {
|
||||
WS_AVAILABLE = 0;
|
||||
WS_FULL = 1;
|
||||
}
|
||||
|
||||
enum JobStatus {
|
||||
JS_PENDING = 0;
|
||||
JS_RUNNING = 1;
|
||||
JS_SUCCESS = 2;
|
||||
JS_FAILED = 3;
|
||||
}
|
||||
|
||||
message SimulateJobRequest {
|
||||
JobType type = 1;
|
||||
Room room = 2;
|
||||
ParticipantInfo participant = 3;
|
||||
}
|
||||
|
||||
message WorkerPing {
|
||||
int64 timestamp = 1;
|
||||
}
|
||||
|
||||
message WorkerPong {
|
||||
int64 last_timestamp = 1;
|
||||
int64 timestamp = 2;
|
||||
}
|
||||
|
||||
message RegisterWorkerRequest {
|
||||
JobType type = 1;
|
||||
//string worker_id = 2;
|
||||
string version = 3;
|
||||
string name = 4;
|
||||
uint32 ping_interval = 5;
|
||||
optional string namespace = 6;
|
||||
ParticipantPermission allowed_permissions = 7;
|
||||
}
|
||||
|
||||
message RegisterWorkerResponse {
|
||||
string worker_id = 1;
|
||||
ServerInfo server_info = 3;
|
||||
}
|
||||
|
||||
message MigrateJobRequest {
|
||||
// string job_id = 1 [deprecated = true];
|
||||
repeated string job_ids = 2;
|
||||
}
|
||||
|
||||
message AvailabilityRequest {
|
||||
Job job = 1;
|
||||
|
||||
// True when the job was previously assigned to another worker but has been
|
||||
// migrated due to different reasons (e.g. worker failure, job migration)
|
||||
bool resuming = 2;
|
||||
}
|
||||
|
||||
|
||||
message AvailabilityResponse {
|
||||
string job_id = 1;
|
||||
bool available = 2;
|
||||
bool supports_resume = 3;
|
||||
|
||||
string participant_name = 4;
|
||||
string participant_identity = 5;
|
||||
string participant_metadata = 6;
|
||||
}
|
||||
|
||||
message UpdateJobStatus {
|
||||
string job_id = 1;
|
||||
|
||||
// The worker can indicate the job end by either specifying SUCCESS or FAILED
|
||||
JobStatus status = 2;
|
||||
|
||||
// metadata shown on the dashboard, useful for debugging
|
||||
string error = 3;
|
||||
|
||||
// the metadata can be updated multiple times
|
||||
// optional string metadata = 4 [deprecated=true];
|
||||
}
|
||||
|
||||
message UpdateWorkerStatus {
|
||||
optional WorkerStatus status = 1;
|
||||
// optional string metadata = 2 [deprecated=true];
|
||||
float load = 3;
|
||||
int32 job_count = 4;
|
||||
}
|
||||
|
||||
message JobAssignment {
|
||||
Job job = 1;
|
||||
optional string url = 2;
|
||||
string token = 3;
|
||||
}
|
||||
|
||||
message JobTermination {
|
||||
string job_id = 1;
|
||||
}
|
||||
185
protobufs/livekit_analytics.proto
Normal file
185
protobufs/livekit_analytics.proto
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package livekit;
|
||||
option go_package = "github.com/livekit/protocol/livekit";
|
||||
option csharp_namespace = "LiveKit.Proto";
|
||||
option ruby_package = "LiveKit::Proto";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "livekit_models.proto";
|
||||
import "livekit_egress.proto";
|
||||
import "livekit_ingress.proto";
|
||||
|
||||
enum StreamType {
|
||||
UPSTREAM = 0;
|
||||
DOWNSTREAM = 1;
|
||||
}
|
||||
|
||||
message AnalyticsVideoLayer {
|
||||
int32 layer = 1;
|
||||
uint32 packets = 2;
|
||||
uint64 bytes = 3;
|
||||
uint32 frames = 4;
|
||||
}
|
||||
|
||||
message AnalyticsStream {
|
||||
uint32 ssrc = 1;
|
||||
uint32 primary_packets = 2;
|
||||
uint64 primary_bytes = 3;
|
||||
uint32 retransmit_packets = 4;
|
||||
uint64 retransmit_bytes = 5;
|
||||
uint32 padding_packets = 6;
|
||||
uint64 padding_bytes = 7;
|
||||
uint32 packets_lost = 8;
|
||||
uint32 frames = 9;
|
||||
uint32 rtt = 10;
|
||||
uint32 jitter = 11;
|
||||
uint32 nacks = 12;
|
||||
uint32 plis = 13;
|
||||
uint32 firs = 14;
|
||||
repeated AnalyticsVideoLayer video_layers = 15;
|
||||
google.protobuf.Timestamp start_time = 17;
|
||||
google.protobuf.Timestamp end_time = 18;
|
||||
}
|
||||
|
||||
message AnalyticsStat {
|
||||
// unique id for this stat
|
||||
string id = 14;
|
||||
string analytics_key = 1;
|
||||
StreamType kind = 2;
|
||||
google.protobuf.Timestamp time_stamp = 3;
|
||||
string node = 4;
|
||||
string room_id = 5;
|
||||
string room_name = 6;
|
||||
string participant_id = 7;
|
||||
string track_id = 8;
|
||||
float score = 9; // average score
|
||||
repeated AnalyticsStream streams = 10;
|
||||
string mime = 11;
|
||||
float min_score = 12;
|
||||
float median_score = 13;
|
||||
|
||||
// NEXT_ID: 15
|
||||
}
|
||||
|
||||
message AnalyticsStats {
|
||||
repeated AnalyticsStat stats = 1;
|
||||
}
|
||||
|
||||
enum AnalyticsEventType {
|
||||
ROOM_CREATED = 0;
|
||||
ROOM_ENDED = 1;
|
||||
PARTICIPANT_JOINED = 2;
|
||||
PARTICIPANT_LEFT = 3;
|
||||
TRACK_PUBLISHED = 4;
|
||||
TRACK_PUBLISH_REQUESTED = 20;
|
||||
TRACK_UNPUBLISHED = 5;
|
||||
TRACK_SUBSCRIBED = 6;
|
||||
TRACK_SUBSCRIBE_REQUESTED = 21;
|
||||
TRACK_SUBSCRIBE_FAILED = 25;
|
||||
TRACK_UNSUBSCRIBED = 7;
|
||||
TRACK_PUBLISHED_UPDATE = 10;
|
||||
TRACK_MUTED = 23;
|
||||
TRACK_UNMUTED = 24;
|
||||
TRACK_PUBLISH_STATS = 26;
|
||||
TRACK_SUBSCRIBE_STATS = 27;
|
||||
PARTICIPANT_ACTIVE = 11;
|
||||
PARTICIPANT_RESUMED = 22;
|
||||
EGRESS_STARTED = 12;
|
||||
EGRESS_ENDED = 13;
|
||||
EGRESS_UPDATED = 28;
|
||||
TRACK_MAX_SUBSCRIBED_VIDEO_QUALITY = 14;
|
||||
RECONNECTED = 15;
|
||||
INGRESS_CREATED = 18;
|
||||
INGRESS_DELETED = 19;
|
||||
INGRESS_STARTED = 16;
|
||||
INGRESS_ENDED = 17;
|
||||
INGRESS_UPDATED = 29;
|
||||
|
||||
// NEXT_ID: 30
|
||||
}
|
||||
|
||||
message AnalyticsClientMeta {
|
||||
string region = 1;
|
||||
string node = 2;
|
||||
string client_addr = 3;
|
||||
uint32 client_connect_time = 4;
|
||||
// udp, tcp, turn
|
||||
string connection_type = 5;
|
||||
ReconnectReason reconnect_reason = 6;
|
||||
optional string geo_hash = 7;
|
||||
optional string country = 8;
|
||||
optional uint32 isp_asn = 9;
|
||||
}
|
||||
|
||||
message AnalyticsEvent {
|
||||
// unique id for this event
|
||||
string id = 25;
|
||||
AnalyticsEventType type = 1;
|
||||
google.protobuf.Timestamp timestamp = 2;
|
||||
string room_id = 3;
|
||||
Room room = 4;
|
||||
string participant_id = 5;
|
||||
ParticipantInfo participant = 6;
|
||||
string track_id = 7;
|
||||
TrackInfo track = 8;
|
||||
string analytics_key = 10;
|
||||
ClientInfo client_info = 11;
|
||||
AnalyticsClientMeta client_meta = 12;
|
||||
string egress_id = 13;
|
||||
string ingress_id = 19;
|
||||
VideoQuality max_subscribed_video_quality = 14;
|
||||
ParticipantInfo publisher = 15;
|
||||
string mime = 16;
|
||||
EgressInfo egress = 17;
|
||||
IngressInfo ingress = 18;
|
||||
string error = 20;
|
||||
RTPStats rtp_stats = 21;
|
||||
int32 video_layer = 22;
|
||||
string node_id = 24;
|
||||
|
||||
// NEXT_ID: 26
|
||||
}
|
||||
|
||||
message AnalyticsEvents {
|
||||
repeated AnalyticsEvent events = 1;
|
||||
}
|
||||
|
||||
message AnalyticsRoomParticipant {
|
||||
string id = 1;
|
||||
string identity = 2;
|
||||
string name = 3;
|
||||
ParticipantInfo.State state = 4;
|
||||
google.protobuf.Timestamp joined_at = 5;
|
||||
}
|
||||
|
||||
message AnalyticsRoom {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
string project_id = 5;
|
||||
google.protobuf.Timestamp created_at = 3;
|
||||
repeated AnalyticsRoomParticipant participants = 4;
|
||||
|
||||
// NEXT_ID: 6
|
||||
}
|
||||
|
||||
message AnalyticsNodeRooms {
|
||||
string node_id = 1;
|
||||
uint64 sequence_number = 2;
|
||||
google.protobuf.Timestamp timestamp = 3;
|
||||
repeated AnalyticsRoom rooms = 4;
|
||||
}
|
||||
419
protobufs/livekit_egress.proto
Normal file
419
protobufs/livekit_egress.proto
Normal file
@@ -0,0 +1,419 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package livekit;
|
||||
|
||||
import "livekit_models.proto";
|
||||
|
||||
option go_package = "github.com/livekit/protocol/livekit";
|
||||
option csharp_namespace = "LiveKit.Proto";
|
||||
option ruby_package = "LiveKit::Proto";
|
||||
|
||||
service Egress {
|
||||
// start recording or streaming a room, participant, or tracks
|
||||
rpc StartRoomCompositeEgress(RoomCompositeEgressRequest) returns (EgressInfo);
|
||||
rpc StartWebEgress(WebEgressRequest) returns (EgressInfo);
|
||||
rpc StartParticipantEgress(ParticipantEgressRequest) returns (EgressInfo);
|
||||
rpc StartTrackCompositeEgress(TrackCompositeEgressRequest) returns (EgressInfo);
|
||||
rpc StartTrackEgress(TrackEgressRequest) returns (EgressInfo);
|
||||
|
||||
// update web composite layout
|
||||
rpc UpdateLayout(UpdateLayoutRequest) returns (EgressInfo);
|
||||
|
||||
// add or remove stream endpoints
|
||||
rpc UpdateStream(UpdateStreamRequest) returns (EgressInfo);
|
||||
|
||||
// list available egress
|
||||
rpc ListEgress(ListEgressRequest) returns (ListEgressResponse);
|
||||
|
||||
// stop a recording or stream
|
||||
rpc StopEgress(StopEgressRequest) returns (EgressInfo);
|
||||
}
|
||||
|
||||
// composite using a web browser
|
||||
message RoomCompositeEgressRequest {
|
||||
string room_name = 1; // required
|
||||
string layout = 2; // (optional)
|
||||
bool audio_only = 3; // (default false)
|
||||
bool video_only = 4; // (default false)
|
||||
string custom_base_url = 5; // template base url (default https://recorder.livekit.io)
|
||||
oneof output { // deprecated (use _output fields)
|
||||
EncodedFileOutput file = 6 [deprecated = true];
|
||||
StreamOutput stream = 7 [deprecated = true];
|
||||
SegmentedFileOutput segments = 10 [deprecated = true];
|
||||
}
|
||||
oneof options {
|
||||
EncodingOptionsPreset preset = 8; // (default H264_720P_30)
|
||||
EncodingOptions advanced = 9; // (optional)
|
||||
}
|
||||
repeated EncodedFileOutput file_outputs = 11;
|
||||
repeated StreamOutput stream_outputs = 12;
|
||||
repeated SegmentedFileOutput segment_outputs = 13;
|
||||
repeated ImageOutput image_outputs = 14;
|
||||
}
|
||||
|
||||
// record any website
|
||||
message WebEgressRequest {
|
||||
string url = 1;
|
||||
bool audio_only = 2;
|
||||
bool video_only = 3;
|
||||
bool await_start_signal = 12;
|
||||
oneof output { // deprecated (use _output fields)
|
||||
EncodedFileOutput file = 4 [deprecated = true];
|
||||
StreamOutput stream = 5 [deprecated = true];
|
||||
SegmentedFileOutput segments = 6 [deprecated = true];
|
||||
}
|
||||
oneof options {
|
||||
EncodingOptionsPreset preset = 7;
|
||||
EncodingOptions advanced = 8;
|
||||
}
|
||||
repeated EncodedFileOutput file_outputs = 9;
|
||||
repeated StreamOutput stream_outputs = 10;
|
||||
repeated SegmentedFileOutput segment_outputs = 11;
|
||||
repeated ImageOutput image_outputs = 13;
|
||||
}
|
||||
|
||||
// record audio and video from a single participant
|
||||
message ParticipantEgressRequest {
|
||||
string room_name = 1; // required
|
||||
string identity = 2; // required
|
||||
bool screen_share = 3; // (default false)
|
||||
oneof options {
|
||||
EncodingOptionsPreset preset = 4; // (default H264_720P_30)
|
||||
EncodingOptions advanced = 5; // (optional)
|
||||
}
|
||||
repeated EncodedFileOutput file_outputs = 6;
|
||||
repeated StreamOutput stream_outputs = 7;
|
||||
repeated SegmentedFileOutput segment_outputs = 8;
|
||||
repeated ImageOutput image_outputs = 9;
|
||||
}
|
||||
|
||||
// containerize up to one audio and one video track
|
||||
message TrackCompositeEgressRequest {
|
||||
string room_name = 1; // required
|
||||
string audio_track_id = 2; // (optional)
|
||||
string video_track_id = 3; // (optional)
|
||||
oneof output { // deprecated (use _output fields)
|
||||
EncodedFileOutput file = 4 [deprecated = true];
|
||||
StreamOutput stream = 5 [deprecated = true];
|
||||
SegmentedFileOutput segments = 8 [deprecated = true];
|
||||
}
|
||||
oneof options {
|
||||
EncodingOptionsPreset preset = 6; // (default H264_720P_30)
|
||||
EncodingOptions advanced = 7; // (optional)
|
||||
}
|
||||
repeated EncodedFileOutput file_outputs = 11;
|
||||
repeated StreamOutput stream_outputs = 12;
|
||||
repeated SegmentedFileOutput segment_outputs = 13;
|
||||
repeated ImageOutput image_outputs = 14;
|
||||
}
|
||||
|
||||
// record tracks individually, without transcoding
|
||||
message TrackEgressRequest {
|
||||
string room_name = 1; // required
|
||||
string track_id = 2; // required
|
||||
oneof output { // required
|
||||
DirectFileOutput file = 3;
|
||||
string websocket_url = 4;
|
||||
}
|
||||
}
|
||||
|
||||
enum EncodedFileType {
|
||||
DEFAULT_FILETYPE = 0; // file type chosen based on codecs
|
||||
MP4 = 1;
|
||||
OGG = 2;
|
||||
}
|
||||
|
||||
message EncodedFileOutput {
|
||||
EncodedFileType file_type = 1; // (optional)
|
||||
string filepath = 2; // see egress docs for templating (default {room_name}-{time})
|
||||
bool disable_manifest = 6; // disable upload of manifest file (default false)
|
||||
oneof output {
|
||||
S3Upload s3 = 3;
|
||||
GCPUpload gcp = 4;
|
||||
AzureBlobUpload azure = 5;
|
||||
AliOSSUpload aliOSS = 7;
|
||||
}
|
||||
}
|
||||
|
||||
enum SegmentedFileProtocol {
|
||||
DEFAULT_SEGMENTED_FILE_PROTOCOL = 0;
|
||||
HLS_PROTOCOL = 1;
|
||||
}
|
||||
|
||||
enum SegmentedFileSuffix {
|
||||
INDEX = 0;
|
||||
TIMESTAMP = 1;
|
||||
}
|
||||
|
||||
// Used to generate HLS segments or other kind of segmented output
|
||||
message SegmentedFileOutput {
|
||||
SegmentedFileProtocol protocol = 1; // (optional)
|
||||
string filename_prefix = 2; // (optional)
|
||||
string playlist_name = 3; // (optional)
|
||||
string live_playlist_name = 11; // (optional, disabled if not provided). Path of a live playlist
|
||||
uint32 segment_duration = 4; // in seconds (optional)
|
||||
SegmentedFileSuffix filename_suffix = 10; // (optional, default INDEX)
|
||||
bool disable_manifest = 8; // disable upload of manifest file (default false)
|
||||
oneof output { // required
|
||||
S3Upload s3 = 5;
|
||||
GCPUpload gcp = 6;
|
||||
AzureBlobUpload azure = 7;
|
||||
AliOSSUpload aliOSS = 9;
|
||||
}
|
||||
}
|
||||
|
||||
message DirectFileOutput {
|
||||
string filepath = 1; // see egress docs for templating (default {track_id}-{time})
|
||||
bool disable_manifest = 5; // disable upload of manifest file (default false)
|
||||
oneof output {
|
||||
S3Upload s3 = 2;
|
||||
GCPUpload gcp = 3;
|
||||
AzureBlobUpload azure = 4;
|
||||
AliOSSUpload aliOSS = 6;
|
||||
}
|
||||
}
|
||||
|
||||
enum ImageFileSuffix {
|
||||
IMAGE_SUFFIX_INDEX = 0;
|
||||
IMAGE_SUFFIX_TIMESTAMP = 1;
|
||||
}
|
||||
|
||||
message ImageOutput {
|
||||
uint32 capture_interval = 1; // in seconds (required)
|
||||
int32 width = 2; // (optional, defaults to track width)
|
||||
int32 height = 3; // (optional, defaults to track height)
|
||||
string filename_prefix = 4; // (optional)
|
||||
ImageFileSuffix filename_suffix = 5; // (optional, default INDEX)
|
||||
ImageCodec image_codec = 6; // (optional)
|
||||
bool disable_manifest = 7; // disable upload of manifest file (default false)
|
||||
oneof output { // required
|
||||
S3Upload s3 = 8;
|
||||
GCPUpload gcp = 9;
|
||||
AzureBlobUpload azure = 10;
|
||||
AliOSSUpload aliOSS = 11;
|
||||
}
|
||||
}
|
||||
|
||||
message S3Upload {
|
||||
string access_key = 1;
|
||||
string secret = 2;
|
||||
string session_token = 11;
|
||||
string region = 3;
|
||||
string endpoint = 4;
|
||||
string bucket = 5;
|
||||
bool force_path_style = 6;
|
||||
map<string, string> metadata = 7;
|
||||
string tagging = 8;
|
||||
string content_disposition = 9; // Content-Disposition header
|
||||
ProxyConfig proxy = 10;
|
||||
}
|
||||
|
||||
message GCPUpload {
|
||||
// service account credentials serialized in JSON "credentials.json"
|
||||
string credentials = 1;
|
||||
string bucket = 2;
|
||||
ProxyConfig proxy = 3;
|
||||
}
|
||||
|
||||
message AzureBlobUpload {
|
||||
string account_name = 1;
|
||||
string account_key = 2;
|
||||
string container_name = 3;
|
||||
}
|
||||
|
||||
message AliOSSUpload {
|
||||
string access_key = 1;
|
||||
string secret = 2;
|
||||
string region = 3;
|
||||
string endpoint = 4;
|
||||
string bucket = 5;
|
||||
}
|
||||
|
||||
message ProxyConfig {
|
||||
string url = 1;
|
||||
string username = 2;
|
||||
string password = 3;
|
||||
}
|
||||
|
||||
enum StreamProtocol {
|
||||
DEFAULT_PROTOCOL = 0; // protocol chosen based on urls
|
||||
RTMP = 1;
|
||||
}
|
||||
|
||||
message StreamOutput {
|
||||
StreamProtocol protocol = 1; // required
|
||||
repeated string urls = 2; // required
|
||||
}
|
||||
|
||||
message EncodingOptions {
|
||||
int32 width = 1; // (default 1920)
|
||||
int32 height = 2; // (default 1080)
|
||||
int32 depth = 3; // (default 24)
|
||||
int32 framerate = 4; // (default 30)
|
||||
AudioCodec audio_codec = 5; // (default OPUS)
|
||||
int32 audio_bitrate = 6; // (default 128)
|
||||
int32 audio_quality = 11; // quality setting on audio encoder
|
||||
int32 audio_frequency = 7; // (default 44100)
|
||||
VideoCodec video_codec = 8; // (default H264_MAIN)
|
||||
int32 video_bitrate = 9; // (default 4500)
|
||||
int32 video_quality = 12; // quality setting on video encoder
|
||||
double key_frame_interval = 10; // in seconds (default 4s for streaming, segment duration for segmented output, encoder default for files)
|
||||
}
|
||||
|
||||
enum EncodingOptionsPreset {
|
||||
H264_720P_30 = 0; // 1280x720, 30fps, 3000kpbs, H.264_MAIN / OPUS
|
||||
H264_720P_60 = 1; // 1280x720, 60fps, 4500kbps, H.264_MAIN / OPUS
|
||||
H264_1080P_30 = 2; // 1920x1080, 30fps, 4500kbps, H.264_MAIN / OPUS
|
||||
H264_1080P_60 = 3; // 1920x1080, 60fps, 6000kbps, H.264_MAIN / OPUS
|
||||
PORTRAIT_H264_720P_30 = 4; // 720x1280, 30fps, 3000kpbs, H.264_MAIN / OPUS
|
||||
PORTRAIT_H264_720P_60 = 5; // 720x1280, 60fps, 4500kbps, H.264_MAIN / OPUS
|
||||
PORTRAIT_H264_1080P_30 = 6; // 1080x1920, 30fps, 4500kbps, H.264_MAIN / OPUS
|
||||
PORTRAIT_H264_1080P_60 = 7; // 1080x1920, 60fps, 6000kbps, H.264_MAIN / OPUS
|
||||
}
|
||||
|
||||
message UpdateLayoutRequest {
|
||||
string egress_id = 1;
|
||||
string layout = 2;
|
||||
}
|
||||
|
||||
message UpdateStreamRequest {
|
||||
string egress_id = 1;
|
||||
repeated string add_output_urls = 2;
|
||||
repeated string remove_output_urls = 3;
|
||||
}
|
||||
|
||||
message ListEgressRequest {
|
||||
string room_name = 1; // (optional, filter by room name)
|
||||
string egress_id = 2; // (optional, filter by egress ID)
|
||||
bool active = 3; // (optional, list active egress only)
|
||||
}
|
||||
|
||||
message ListEgressResponse {
|
||||
repeated EgressInfo items = 1;
|
||||
}
|
||||
|
||||
message StopEgressRequest {
|
||||
string egress_id = 1;
|
||||
}
|
||||
|
||||
enum EgressStatus {
|
||||
EGRESS_STARTING = 0;
|
||||
EGRESS_ACTIVE = 1;
|
||||
EGRESS_ENDING = 2;
|
||||
EGRESS_COMPLETE = 3;
|
||||
EGRESS_FAILED = 4;
|
||||
EGRESS_ABORTED = 5;
|
||||
EGRESS_LIMIT_REACHED = 6;
|
||||
}
|
||||
|
||||
message EgressInfo {
|
||||
string egress_id = 1;
|
||||
string room_id = 2;
|
||||
string room_name = 13;
|
||||
EgressStatus status = 3;
|
||||
int64 started_at = 10;
|
||||
int64 ended_at = 11;
|
||||
int64 updated_at = 18;
|
||||
string details = 21;
|
||||
string error = 9;
|
||||
int32 error_code = 22;
|
||||
oneof request {
|
||||
RoomCompositeEgressRequest room_composite = 4;
|
||||
WebEgressRequest web = 14;
|
||||
ParticipantEgressRequest participant = 19;
|
||||
TrackCompositeEgressRequest track_composite = 5;
|
||||
TrackEgressRequest track = 6;
|
||||
}
|
||||
oneof result { // deprecated (use _result fields)
|
||||
StreamInfoList stream = 7 [deprecated = true];
|
||||
FileInfo file = 8 [deprecated = true];
|
||||
SegmentsInfo segments = 12 [deprecated = true];
|
||||
}
|
||||
repeated StreamInfo stream_results = 15;
|
||||
repeated FileInfo file_results = 16;
|
||||
repeated SegmentsInfo segment_results = 17;
|
||||
repeated ImagesInfo image_results = 20;
|
||||
|
||||
// next ID: 23
|
||||
}
|
||||
|
||||
message StreamInfoList {
|
||||
option deprecated = true;
|
||||
repeated StreamInfo info = 1;
|
||||
}
|
||||
|
||||
message StreamInfo {
|
||||
enum Status {
|
||||
ACTIVE = 0;
|
||||
FINISHED = 1;
|
||||
FAILED = 2;
|
||||
}
|
||||
|
||||
string url = 1;
|
||||
int64 started_at = 2;
|
||||
int64 ended_at = 3;
|
||||
int64 duration = 4;
|
||||
Status status = 5;
|
||||
string error = 6;
|
||||
}
|
||||
|
||||
message FileInfo {
|
||||
string filename = 1;
|
||||
int64 started_at = 2;
|
||||
int64 ended_at = 3;
|
||||
int64 duration = 6;
|
||||
int64 size = 4;
|
||||
string location = 5;
|
||||
}
|
||||
|
||||
message SegmentsInfo {
|
||||
string playlist_name = 1;
|
||||
string live_playlist_name = 8;
|
||||
int64 duration = 2;
|
||||
int64 size = 3;
|
||||
string playlist_location = 4;
|
||||
string live_playlist_location = 9;
|
||||
int64 segment_count = 5;
|
||||
int64 started_at = 6;
|
||||
int64 ended_at = 7;
|
||||
}
|
||||
|
||||
message ImagesInfo {
|
||||
int64 image_count = 1;
|
||||
int64 started_at = 2;
|
||||
int64 ended_at = 3;
|
||||
}
|
||||
|
||||
message AutoParticipantEgress {
|
||||
oneof options {
|
||||
EncodingOptionsPreset preset = 1; // (default H264_720P_30)
|
||||
EncodingOptions advanced = 2; // (optional)
|
||||
}
|
||||
repeated EncodedFileOutput file_outputs = 3;
|
||||
repeated SegmentedFileOutput segment_outputs = 4;
|
||||
}
|
||||
|
||||
message AutoTrackEgress {
|
||||
string filepath = 1; // see docs for templating (default {track_id}-{time})
|
||||
bool disable_manifest = 5; // disables upload of json manifest file (default false)
|
||||
oneof output {
|
||||
S3Upload s3 = 2;
|
||||
GCPUpload gcp = 3;
|
||||
AzureBlobUpload azure = 4;
|
||||
AliOSSUpload aliOSS = 6;
|
||||
}
|
||||
}
|
||||
206
protobufs/livekit_ingress.proto
Normal file
206
protobufs/livekit_ingress.proto
Normal file
@@ -0,0 +1,206 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package livekit;
|
||||
|
||||
import "livekit_models.proto";
|
||||
|
||||
option go_package = "github.com/livekit/protocol/livekit";
|
||||
option csharp_namespace = "LiveKit.Proto";
|
||||
option ruby_package = "LiveKit::Proto";
|
||||
|
||||
service Ingress {
|
||||
// Create a new Ingress
|
||||
rpc CreateIngress(CreateIngressRequest) returns (IngressInfo);
|
||||
// Update an existing Ingress. Ingress can only be updated when it's in ENDPOINT_WAITING state.
|
||||
rpc UpdateIngress(UpdateIngressRequest) returns (IngressInfo);
|
||||
rpc ListIngress(ListIngressRequest) returns (ListIngressResponse);
|
||||
rpc DeleteIngress(DeleteIngressRequest) returns (IngressInfo);
|
||||
}
|
||||
|
||||
message CreateIngressRequest {
|
||||
IngressInput input_type = 1;
|
||||
// Where to pull media from, only for URL input type
|
||||
string url = 9;
|
||||
// User provided identifier for the ingress
|
||||
string name = 2;
|
||||
// room to publish to
|
||||
string room_name = 3;
|
||||
// publish as participant
|
||||
string participant_identity = 4;
|
||||
// name of publishing participant (used for display only)
|
||||
string participant_name = 5;
|
||||
// metadata associated with the publishing participant
|
||||
string participant_metadata = 10;
|
||||
// [depreacted ] whether to pass through the incoming media without transcoding, only compatible with some input types. Use `enable_transcoding` instead.
|
||||
bool bypass_transcoding = 8 [deprecated = true];
|
||||
// Whether to transcode the ingested media. Only WHIP supports disabling transcoding currently. WHIP will default to transcoding disabled. Replaces `bypass_transcoding.
|
||||
optional bool enable_transcoding = 11;
|
||||
IngressAudioOptions audio = 6;
|
||||
IngressVideoOptions video = 7;
|
||||
|
||||
// NEXT_ID: 12
|
||||
}
|
||||
|
||||
enum IngressInput {
|
||||
RTMP_INPUT = 0;
|
||||
WHIP_INPUT = 1;
|
||||
URL_INPUT = 2; // Pull from the provided URL. Only HTTP url are supported, serving either a single media file or a HLS stream
|
||||
// FILE_INPUT = 3;
|
||||
// SRT_INPUT = 4;
|
||||
}
|
||||
|
||||
message IngressAudioOptions {
|
||||
string name = 1;
|
||||
TrackSource source = 2;
|
||||
oneof encoding_options {
|
||||
IngressAudioEncodingPreset preset = 3;
|
||||
IngressAudioEncodingOptions options = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message IngressVideoOptions {
|
||||
string name = 1;
|
||||
TrackSource source = 2;
|
||||
oneof encoding_options {
|
||||
IngressVideoEncodingPreset preset = 3;
|
||||
IngressVideoEncodingOptions options = 4;
|
||||
}
|
||||
}
|
||||
|
||||
enum IngressAudioEncodingPreset {
|
||||
OPUS_STEREO_96KBPS = 0; // OPUS, 2 channels, 96kbps
|
||||
OPUS_MONO_64KBS = 1; // OPUS, 1 channel, 64kbps
|
||||
}
|
||||
|
||||
enum IngressVideoEncodingPreset {
|
||||
H264_720P_30FPS_3_LAYERS = 0; // 1280x720, 30fps, 1900kbps main layer, 3 layers total
|
||||
H264_1080P_30FPS_3_LAYERS = 1; // 1980x1080, 30fps, 3500kbps main layer, 3 layers total
|
||||
H264_540P_25FPS_2_LAYERS = 2; // 960x540, 25fps, 1000kbps main layer, 2 layers total
|
||||
H264_720P_30FPS_1_LAYER = 3; // 1280x720, 30fps, 1900kbps, no simulcast
|
||||
H264_1080P_30FPS_1_LAYER = 4; // 1980x1080, 30fps, 3500kbps, no simulcast
|
||||
H264_720P_30FPS_3_LAYERS_HIGH_MOTION = 5; // 1280x720, 30fps, 2500kbps main layer, 3 layers total, higher bitrate for high motion, harder to encode content
|
||||
H264_1080P_30FPS_3_LAYERS_HIGH_MOTION = 6; // 1980x1080, 30fps, 4500kbps main layer, 3 layers total, higher bitrate for high motion, harder to encode content
|
||||
H264_540P_25FPS_2_LAYERS_HIGH_MOTION = 7; // 960x540, 25fps, 1300kbps main layer, 2 layers total, higher bitrate for high motion, harder to encode content
|
||||
H264_720P_30FPS_1_LAYER_HIGH_MOTION = 8; // 1280x720, 30fps, 2500kbps, no simulcast, higher bitrate for high motion, harder to encode content
|
||||
H264_1080P_30FPS_1_LAYER_HIGH_MOTION = 9; // 1980x1080, 30fps, 4500kbps, no simulcast, higher bitrate for high motion, harder to encode content
|
||||
}
|
||||
|
||||
message IngressAudioEncodingOptions {
|
||||
// desired audio codec to publish to room
|
||||
AudioCodec audio_codec = 1;
|
||||
uint32 bitrate = 2;
|
||||
bool disable_dtx = 3;
|
||||
uint32 channels = 4;
|
||||
}
|
||||
|
||||
message IngressVideoEncodingOptions {
|
||||
// desired codec to publish to room
|
||||
VideoCodec video_codec = 1;
|
||||
double frame_rate = 2;
|
||||
// simulcast layers to publish, when empty, should usually be set to layers at 1/2 and 1/4 of the dimensions
|
||||
repeated VideoLayer layers = 3;
|
||||
}
|
||||
|
||||
message IngressInfo {
|
||||
string ingress_id = 1;
|
||||
string name = 2;
|
||||
string stream_key = 3;
|
||||
string url = 4; // URL to point the encoder to for push (RTMP, WHIP), or location to pull media from for pull (URL)
|
||||
// for RTMP input, it'll be a rtmp:// URL
|
||||
// for FILE input, it'll be a http:// URL
|
||||
// for SRT input, it'll be a srt:// URL
|
||||
IngressInput input_type = 5;
|
||||
bool bypass_transcoding = 13 [deprecated = true];
|
||||
optional bool enable_transcoding = 15;
|
||||
IngressAudioOptions audio = 6;
|
||||
IngressVideoOptions video = 7;
|
||||
string room_name = 8;
|
||||
string participant_identity = 9;
|
||||
string participant_name = 10;
|
||||
string participant_metadata = 14;
|
||||
bool reusable = 11;
|
||||
IngressState state = 12; // Description of error/stream non compliance and debug info for publisher otherwise (received bitrate, resolution, bandwidth)
|
||||
|
||||
// NEXT_ID: 16
|
||||
}
|
||||
|
||||
message IngressState {
|
||||
enum Status {
|
||||
ENDPOINT_INACTIVE = 0;
|
||||
ENDPOINT_BUFFERING = 1;
|
||||
ENDPOINT_PUBLISHING = 2;
|
||||
ENDPOINT_ERROR = 3;
|
||||
ENDPOINT_COMPLETE = 4;
|
||||
}
|
||||
|
||||
Status status = 1;
|
||||
string error = 2; // Error/non compliance description if any
|
||||
InputVideoState video = 3;
|
||||
InputAudioState audio = 4;
|
||||
string room_id = 5; // ID of the current/previous room published to
|
||||
int64 started_at = 7;
|
||||
int64 ended_at = 8;
|
||||
int64 updated_at = 10;
|
||||
string resource_id = 9;
|
||||
repeated TrackInfo tracks = 6;
|
||||
|
||||
// next ID: 11
|
||||
}
|
||||
|
||||
message InputVideoState {
|
||||
string mime_type = 1;
|
||||
uint32 average_bitrate = 2;
|
||||
uint32 width = 3;
|
||||
uint32 height = 4;
|
||||
double framerate = 5;
|
||||
}
|
||||
|
||||
message InputAudioState {
|
||||
string mime_type = 1;
|
||||
uint32 average_bitrate = 2;
|
||||
uint32 channels = 3;
|
||||
uint32 sample_rate = 4;
|
||||
}
|
||||
|
||||
message UpdateIngressRequest {
|
||||
string ingress_id = 1;
|
||||
string name = 2;
|
||||
string room_name = 3;
|
||||
string participant_identity = 4;
|
||||
string participant_name = 5;
|
||||
string participant_metadata = 9;
|
||||
optional bool bypass_transcoding = 8 [deprecated = true];
|
||||
optional bool enable_transcoding = 10;
|
||||
IngressAudioOptions audio = 6;
|
||||
IngressVideoOptions video = 7;
|
||||
|
||||
// NEXT_ID: 11
|
||||
}
|
||||
|
||||
message ListIngressRequest {
|
||||
// when blank, lists all ingress endpoints
|
||||
string room_name = 1; // (optional, filter by room name)
|
||||
string ingress_id = 2; // (optional, filter by ingress ID)
|
||||
}
|
||||
|
||||
message ListIngressResponse {
|
||||
repeated IngressInfo items = 1;
|
||||
}
|
||||
|
||||
message DeleteIngressRequest {
|
||||
string ingress_id = 1;
|
||||
}
|
||||
158
protobufs/livekit_internal.proto
Normal file
158
protobufs/livekit_internal.proto
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package livekit;
|
||||
option go_package = "github.com/livekit/protocol/livekit";
|
||||
option csharp_namespace = "LiveKit.Proto";
|
||||
option ruby_package = "LiveKit::Proto";
|
||||
|
||||
// internal protos, not exposed to clients
|
||||
import "livekit_models.proto";
|
||||
import "livekit_egress.proto";
|
||||
import "livekit_agent.proto";
|
||||
|
||||
enum NodeType {
|
||||
SERVER = 0;
|
||||
CONTROLLER = 1;
|
||||
MEDIA = 2;
|
||||
TURN = 4;
|
||||
SWEEPER = 5;
|
||||
DIRECTOR = 6;
|
||||
}
|
||||
|
||||
enum NodeState {
|
||||
STARTING_UP = 0;
|
||||
SERVING = 1;
|
||||
SHUTTING_DOWN = 2;
|
||||
}
|
||||
|
||||
message Node {
|
||||
string id = 1;
|
||||
string ip = 2;
|
||||
uint32 num_cpus = 3;
|
||||
NodeStats stats = 4;
|
||||
NodeType type = 5;
|
||||
NodeState state = 6;
|
||||
string region = 7;
|
||||
}
|
||||
|
||||
message NodeStats {
|
||||
// when server was started
|
||||
int64 started_at = 1;
|
||||
// when server last reported its status
|
||||
int64 updated_at = 2;
|
||||
|
||||
// room
|
||||
int32 num_rooms = 3;
|
||||
int32 num_clients = 4;
|
||||
int32 num_tracks_in = 5;
|
||||
int32 num_tracks_out = 6;
|
||||
int32 num_track_publish_attempts = 36;
|
||||
float track_publish_attempts_per_sec = 37;
|
||||
int32 num_track_publish_success = 38;
|
||||
float track_publish_success_per_sec = 39;
|
||||
int32 num_track_subscribe_attempts = 40;
|
||||
float track_subscribe_attempts_per_sec = 41;
|
||||
int32 num_track_subscribe_success = 42;
|
||||
float track_subscribe_success_per_sec = 43;
|
||||
|
||||
// packet
|
||||
uint64 bytes_in = 7;
|
||||
uint64 bytes_out = 8;
|
||||
uint64 packets_in = 9;
|
||||
uint64 packets_out = 10;
|
||||
uint64 nack_total = 11;
|
||||
float bytes_in_per_sec = 12;
|
||||
float bytes_out_per_sec = 13;
|
||||
float packets_in_per_sec = 14;
|
||||
float packets_out_per_sec = 15;
|
||||
float nack_per_sec = 16;
|
||||
|
||||
// system
|
||||
uint32 num_cpus = 17;
|
||||
float load_avg_last1min = 18;
|
||||
float load_avg_last5min = 19;
|
||||
float load_avg_last15min = 20;
|
||||
float cpu_load = 21;
|
||||
float memory_load = 33; // deprecated
|
||||
uint64 memory_total = 34;
|
||||
uint64 memory_used = 35;
|
||||
uint32 sys_packets_out = 28;
|
||||
uint32 sys_packets_dropped = 29;
|
||||
float sys_packets_out_per_sec = 30;
|
||||
float sys_packets_dropped_per_sec = 31;
|
||||
float sys_packets_dropped_pct_per_sec = 32;
|
||||
|
||||
// retransmissions
|
||||
uint64 retransmit_bytes_out = 22;
|
||||
uint64 retransmit_packets_out = 23;
|
||||
float retransmit_bytes_out_per_sec = 24;
|
||||
float retransmit_packets_out_per_sec = 25;
|
||||
|
||||
// participant joins
|
||||
uint64 participant_signal_connected = 26;
|
||||
float participant_signal_connected_per_sec = 27;
|
||||
uint64 participant_rtc_connected = 44;
|
||||
float participant_rtc_connected_per_sec = 45;
|
||||
uint64 participant_rtc_init = 46;
|
||||
float participant_rtc_init_per_sec = 47;
|
||||
|
||||
// forward metrics
|
||||
uint32 forward_latency = 48;
|
||||
uint32 forward_jitter = 49;
|
||||
|
||||
// NEXT ID: 50
|
||||
}
|
||||
|
||||
message StartSession {
|
||||
string room_name = 1;
|
||||
string identity = 2;
|
||||
string connection_id = 3;
|
||||
// if a client is reconnecting (i.e. resume instead of restart)
|
||||
bool reconnect = 4;
|
||||
bool auto_subscribe = 9;
|
||||
bool hidden = 10;
|
||||
ClientInfo client = 11;
|
||||
bool recorder = 12;
|
||||
string name = 13;
|
||||
// A user's ClaimGrants serialized in JSON
|
||||
string grants_json = 14;
|
||||
bool adaptive_stream = 15;
|
||||
//if reconnect, client will set current sid
|
||||
string participant_id = 16;
|
||||
ReconnectReason reconnect_reason = 17;
|
||||
optional bool subscriber_allow_pause = 18;
|
||||
}
|
||||
|
||||
// room info that should not be returned to clients
|
||||
message RoomInternal {
|
||||
AutoTrackEgress track_egress = 1;
|
||||
AutoParticipantEgress participant_egress = 2;
|
||||
PlayoutDelay playout_delay = 3;
|
||||
repeated CreateAgentJobDefinitionRequest agents = 5;
|
||||
bool sync_streams = 4;
|
||||
}
|
||||
|
||||
enum ICECandidateType {
|
||||
ICT_NONE = 0;
|
||||
ICT_TCP = 1;
|
||||
ICT_TLS = 2;
|
||||
}
|
||||
|
||||
message ICEConfig {
|
||||
ICECandidateType preference_subscriber = 1;
|
||||
ICECandidateType preference_publisher = 2;
|
||||
}
|
||||
493
protobufs/livekit_models.proto
Normal file
493
protobufs/livekit_models.proto
Normal file
@@ -0,0 +1,493 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package livekit;
|
||||
option go_package = "github.com/livekit/protocol/livekit";
|
||||
option csharp_namespace = "LiveKit.Proto";
|
||||
option ruby_package = "LiveKit::Proto";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
message Room {
|
||||
string sid = 1;
|
||||
string name = 2;
|
||||
uint32 empty_timeout = 3;
|
||||
uint32 departure_timeout = 14;
|
||||
uint32 max_participants = 4;
|
||||
int64 creation_time = 5;
|
||||
string turn_password = 6;
|
||||
repeated Codec enabled_codecs = 7;
|
||||
string metadata = 8;
|
||||
uint32 num_participants = 9;
|
||||
uint32 num_publishers = 11;
|
||||
bool active_recording = 10;
|
||||
TimedVersion version = 13;
|
||||
|
||||
// NEXT-ID: 15
|
||||
}
|
||||
|
||||
message Codec {
|
||||
string mime = 1;
|
||||
string fmtp_line = 2;
|
||||
}
|
||||
|
||||
enum AudioCodec {
|
||||
DEFAULT_AC = 0;
|
||||
OPUS = 1;
|
||||
AAC = 2;
|
||||
}
|
||||
|
||||
enum VideoCodec {
|
||||
DEFAULT_VC = 0;
|
||||
H264_BASELINE = 1;
|
||||
H264_MAIN = 2;
|
||||
H264_HIGH = 3;
|
||||
VP8 = 4;
|
||||
}
|
||||
|
||||
enum ImageCodec {
|
||||
IC_DEFAULT = 0;
|
||||
IC_JPEG = 1;
|
||||
}
|
||||
|
||||
message PlayoutDelay {
|
||||
bool enabled = 1;
|
||||
uint32 min = 2;
|
||||
uint32 max = 3;
|
||||
}
|
||||
|
||||
message ParticipantPermission {
|
||||
// allow participant to subscribe to other tracks in the room
|
||||
bool can_subscribe = 1;
|
||||
// allow participant to publish new tracks to room
|
||||
bool can_publish = 2;
|
||||
// allow participant to publish data
|
||||
bool can_publish_data = 3;
|
||||
// sources that are allowed to be published
|
||||
repeated TrackSource can_publish_sources = 9;
|
||||
// indicates that it's hidden to others
|
||||
bool hidden = 7;
|
||||
// indicates it's a recorder instance
|
||||
// deprecated: use ParticipantInfo.kind instead
|
||||
bool recorder = 8 [deprecated=true];
|
||||
// indicates that participant can update own metadata and attributes
|
||||
bool can_update_metadata = 10;
|
||||
// indicates that participant is an agent
|
||||
// deprecated: use ParticipantInfo.kind instead
|
||||
bool agent = 11 [deprecated=true];
|
||||
|
||||
// NEXT_ID: 12
|
||||
}
|
||||
|
||||
message ParticipantInfo {
|
||||
enum State {
|
||||
// websocket' connected, but not offered yet
|
||||
JOINING = 0;
|
||||
// server received client offer
|
||||
JOINED = 1;
|
||||
// ICE connectivity established
|
||||
ACTIVE = 2;
|
||||
// WS disconnected
|
||||
DISCONNECTED = 3;
|
||||
}
|
||||
enum Kind {
|
||||
// standard participants, e.g. web clients
|
||||
STANDARD = 0;
|
||||
// only ingests streams
|
||||
INGRESS = 1;
|
||||
// only consumes streams
|
||||
EGRESS = 2;
|
||||
// SIP participants
|
||||
SIP = 3;
|
||||
// LiveKit agents
|
||||
AGENT = 4;
|
||||
}
|
||||
string sid = 1;
|
||||
string identity = 2;
|
||||
State state = 3;
|
||||
repeated TrackInfo tracks = 4;
|
||||
string metadata = 5;
|
||||
// timestamp when participant joined room, in seconds
|
||||
int64 joined_at = 6;
|
||||
string name = 9;
|
||||
uint32 version = 10;
|
||||
ParticipantPermission permission = 11;
|
||||
string region = 12;
|
||||
// indicates the participant has an active publisher connection
|
||||
// and can publish to the server
|
||||
bool is_publisher = 13;
|
||||
Kind kind = 14;
|
||||
map<string, string> attributes = 15;
|
||||
}
|
||||
|
||||
enum TrackType {
|
||||
AUDIO = 0;
|
||||
VIDEO = 1;
|
||||
DATA = 2;
|
||||
}
|
||||
|
||||
enum TrackSource {
|
||||
UNKNOWN = 0;
|
||||
CAMERA = 1;
|
||||
MICROPHONE = 2;
|
||||
SCREEN_SHARE = 3;
|
||||
SCREEN_SHARE_AUDIO = 4;
|
||||
}
|
||||
|
||||
message Encryption {
|
||||
enum Type {
|
||||
NONE=0;
|
||||
GCM=1;
|
||||
CUSTOM=2;
|
||||
}
|
||||
}
|
||||
|
||||
message SimulcastCodecInfo {
|
||||
string mime_type = 1;
|
||||
string mid = 2;
|
||||
string cid = 3;
|
||||
repeated VideoLayer layers = 4;
|
||||
}
|
||||
|
||||
message TrackInfo {
|
||||
string sid = 1;
|
||||
TrackType type = 2;
|
||||
string name = 3;
|
||||
bool muted = 4;
|
||||
// original width of video (unset for audio)
|
||||
// clients may receive a lower resolution version with simulcast
|
||||
uint32 width = 5;
|
||||
// original height of video (unset for audio)
|
||||
uint32 height = 6;
|
||||
// true if track is simulcasted
|
||||
bool simulcast = 7;
|
||||
// true if DTX (Discontinuous Transmission) is disabled for audio
|
||||
bool disable_dtx = 8;
|
||||
// source of media
|
||||
TrackSource source = 9;
|
||||
repeated VideoLayer layers = 10;
|
||||
// mime type of codec
|
||||
string mime_type = 11;
|
||||
string mid = 12;
|
||||
repeated SimulcastCodecInfo codecs = 13;
|
||||
bool stereo = 14;
|
||||
// true if RED (Redundant Encoding) is disabled for audio
|
||||
bool disable_red = 15;
|
||||
Encryption.Type encryption = 16;
|
||||
string stream = 17;
|
||||
TimedVersion version = 18;
|
||||
repeated AudioTrackFeature audio_features = 19;
|
||||
}
|
||||
|
||||
enum VideoQuality {
|
||||
LOW = 0;
|
||||
MEDIUM = 1;
|
||||
HIGH = 2;
|
||||
OFF = 3;
|
||||
}
|
||||
|
||||
// provide information about available spatial layers
|
||||
message VideoLayer {
|
||||
// for tracks with a single layer, this should be HIGH
|
||||
VideoQuality quality = 1;
|
||||
uint32 width = 2;
|
||||
uint32 height = 3;
|
||||
// target bitrate in bit per second (bps), server will measure actual
|
||||
uint32 bitrate = 4;
|
||||
uint32 ssrc = 5;
|
||||
}
|
||||
|
||||
// new DataPacket API
|
||||
message DataPacket {
|
||||
enum Kind {
|
||||
RELIABLE = 0;
|
||||
LOSSY = 1;
|
||||
}
|
||||
Kind kind = 1 [deprecated=true];
|
||||
// participant identity of user that sent the message
|
||||
string participant_identity = 4;
|
||||
// identities of participants who will receive the message (sent to all by default)
|
||||
repeated string destination_identities = 5;
|
||||
oneof value {
|
||||
UserPacket user = 2;
|
||||
ActiveSpeakerUpdate speaker = 3 [deprecated=true];
|
||||
SipDTMF sip_dtmf = 6;
|
||||
Transcription transcription = 7;
|
||||
}
|
||||
// NEXT_ID: 8
|
||||
}
|
||||
|
||||
message ActiveSpeakerUpdate {
|
||||
repeated SpeakerInfo speakers = 1;
|
||||
}
|
||||
|
||||
message SpeakerInfo {
|
||||
string sid = 1;
|
||||
// audio level, 0-1.0, 1 is loudest
|
||||
float level = 2;
|
||||
// true if speaker is currently active
|
||||
bool active = 3;
|
||||
}
|
||||
|
||||
message UserPacket {
|
||||
// participant ID of user that sent the message
|
||||
string participant_sid = 1 [deprecated=true];
|
||||
string participant_identity = 5 [deprecated=true];
|
||||
// user defined payload
|
||||
bytes payload = 2;
|
||||
// the ID of the participants who will receive the message (sent to all by default)
|
||||
repeated string destination_sids = 3 [deprecated=true];
|
||||
// identities of participants who will receive the message (sent to all by default)
|
||||
repeated string destination_identities = 6 [deprecated=true];
|
||||
// topic under which the message was published
|
||||
optional string topic = 4;
|
||||
// Unique ID to indentify the message
|
||||
optional string id = 8;
|
||||
// start and end time allow relating the message to specific media time
|
||||
optional uint64 start_time = 9;
|
||||
optional uint64 end_time = 10;
|
||||
|
||||
// NEXT_ID: 11
|
||||
}
|
||||
|
||||
message SipDTMF {
|
||||
uint32 code = 3;
|
||||
string digit = 4;
|
||||
}
|
||||
|
||||
message Transcription {
|
||||
// Participant that got its speech transcribed
|
||||
string transcribed_participant_identity = 2;
|
||||
string track_id = 3;
|
||||
repeated TranscriptionSegment segments = 4;
|
||||
|
||||
// NEXT_ID: 6
|
||||
}
|
||||
|
||||
message TranscriptionSegment {
|
||||
string id = 1;
|
||||
string text = 2;
|
||||
uint64 start_time = 3;
|
||||
uint64 end_time = 4;
|
||||
bool final = 5;
|
||||
string language = 6;
|
||||
}
|
||||
|
||||
enum ConnectionQuality {
|
||||
POOR = 0;
|
||||
GOOD = 1;
|
||||
EXCELLENT = 2;
|
||||
LOST = 3;
|
||||
}
|
||||
|
||||
message ParticipantTracks {
|
||||
// participant ID of participant to whom the tracks belong
|
||||
string participant_sid = 1;
|
||||
repeated string track_sids = 2;
|
||||
}
|
||||
|
||||
// details about the server
|
||||
message ServerInfo {
|
||||
enum Edition {
|
||||
Standard = 0;
|
||||
Cloud = 1;
|
||||
}
|
||||
Edition edition = 1;
|
||||
string version = 2;
|
||||
int32 protocol = 3;
|
||||
string region = 4;
|
||||
string node_id = 5;
|
||||
// additional debugging information. sent only if server is in development mode
|
||||
string debug_info = 6;
|
||||
int32 agent_protocol = 7;
|
||||
}
|
||||
|
||||
// details about the client
|
||||
message ClientInfo {
|
||||
enum SDK {
|
||||
UNKNOWN = 0;
|
||||
JS = 1;
|
||||
SWIFT = 2;
|
||||
ANDROID = 3;
|
||||
FLUTTER = 4;
|
||||
GO = 5;
|
||||
UNITY = 6;
|
||||
REACT_NATIVE = 7;
|
||||
RUST = 8;
|
||||
PYTHON = 9;
|
||||
CPP = 10;
|
||||
}
|
||||
|
||||
SDK sdk = 1;
|
||||
string version = 2;
|
||||
int32 protocol = 3;
|
||||
string os = 4;
|
||||
string os_version = 5;
|
||||
string device_model = 6;
|
||||
string browser = 7;
|
||||
string browser_version = 8;
|
||||
string address = 9;
|
||||
// wifi, wired, cellular, vpn, empty if not known
|
||||
string network = 10;
|
||||
}
|
||||
|
||||
// server provided client configuration
|
||||
message ClientConfiguration {
|
||||
VideoConfiguration video = 1;
|
||||
VideoConfiguration screen = 2;
|
||||
|
||||
ClientConfigSetting resume_connection = 3;
|
||||
DisabledCodecs disabled_codecs = 4;
|
||||
ClientConfigSetting force_relay = 5;
|
||||
}
|
||||
|
||||
enum ClientConfigSetting {
|
||||
UNSET = 0;
|
||||
DISABLED = 1;
|
||||
ENABLED = 2;
|
||||
}
|
||||
|
||||
message VideoConfiguration {
|
||||
ClientConfigSetting hardware_encoder = 1;
|
||||
}
|
||||
|
||||
message DisabledCodecs {
|
||||
// disabled for both publish and subscribe
|
||||
repeated Codec codecs = 1;
|
||||
// only disable for publish
|
||||
repeated Codec publish = 2;
|
||||
}
|
||||
|
||||
enum DisconnectReason {
|
||||
UNKNOWN_REASON = 0;
|
||||
CLIENT_INITIATED = 1;
|
||||
DUPLICATE_IDENTITY = 2;
|
||||
SERVER_SHUTDOWN = 3;
|
||||
PARTICIPANT_REMOVED = 4;
|
||||
ROOM_DELETED = 5;
|
||||
STATE_MISMATCH = 6;
|
||||
JOIN_FAILURE = 7;
|
||||
MIGRATION = 8;
|
||||
SIGNAL_CLOSE = 9;
|
||||
}
|
||||
|
||||
message RTPDrift {
|
||||
google.protobuf.Timestamp start_time = 1;
|
||||
google.protobuf.Timestamp end_time = 2;
|
||||
double duration = 3;
|
||||
|
||||
uint64 start_timestamp = 4;
|
||||
uint64 end_timestamp = 5;
|
||||
uint64 rtp_clock_ticks = 6;
|
||||
int64 drift_samples = 7;
|
||||
double drift_ms = 8;
|
||||
double clock_rate = 9;
|
||||
}
|
||||
|
||||
message RTPStats {
|
||||
google.protobuf.Timestamp start_time = 1;
|
||||
google.protobuf.Timestamp end_time = 2;
|
||||
double duration = 3;
|
||||
|
||||
uint32 packets = 4;
|
||||
double packet_rate = 5;
|
||||
|
||||
uint64 bytes = 6;
|
||||
uint64 header_bytes = 39;
|
||||
double bitrate = 7;
|
||||
|
||||
uint32 packets_lost = 8;
|
||||
double packet_loss_rate = 9;
|
||||
float packet_loss_percentage = 10;
|
||||
|
||||
uint32 packets_duplicate = 11;
|
||||
double packet_duplicate_rate = 12;
|
||||
|
||||
uint64 bytes_duplicate = 13;
|
||||
uint64 header_bytes_duplicate = 40;
|
||||
double bitrate_duplicate = 14;
|
||||
|
||||
uint32 packets_padding = 15;
|
||||
double packet_padding_rate = 16;
|
||||
|
||||
uint64 bytes_padding = 17;
|
||||
uint64 header_bytes_padding = 41;
|
||||
double bitrate_padding = 18;
|
||||
|
||||
uint32 packets_out_of_order = 19;
|
||||
|
||||
uint32 frames = 20;
|
||||
double frame_rate = 21;
|
||||
|
||||
double jitter_current = 22;
|
||||
double jitter_max = 23;
|
||||
|
||||
map<int32, uint32> gap_histogram = 24;
|
||||
|
||||
uint32 nacks = 25;
|
||||
uint32 nack_acks = 37;
|
||||
uint32 nack_misses = 26;
|
||||
uint32 nack_repeated = 38;
|
||||
|
||||
uint32 plis = 27;
|
||||
google.protobuf.Timestamp last_pli = 28;
|
||||
|
||||
uint32 firs = 29;
|
||||
google.protobuf.Timestamp last_fir = 30;
|
||||
|
||||
uint32 rtt_current = 31;
|
||||
uint32 rtt_max = 32;
|
||||
|
||||
uint32 key_frames = 33;
|
||||
google.protobuf.Timestamp last_key_frame = 34;
|
||||
|
||||
uint32 layer_lock_plis = 35;
|
||||
google.protobuf.Timestamp last_layer_lock_pli = 36;
|
||||
|
||||
RTPDrift packet_drift = 44;
|
||||
RTPDrift report_drift = 45;
|
||||
RTPDrift rebased_report_drift = 46;
|
||||
// NEXT_ID: 47
|
||||
}
|
||||
|
||||
message TimedVersion {
|
||||
int64 unix_micro = 1;
|
||||
int32 ticks = 2;
|
||||
}
|
||||
|
||||
enum ReconnectReason {
|
||||
RR_UNKNOWN = 0;
|
||||
RR_SIGNAL_DISCONNECTED = 1;
|
||||
RR_PUBLISHER_FAILED = 2;
|
||||
RR_SUBSCRIBER_FAILED = 3;
|
||||
RR_SWITCH_CANDIDATE = 4;
|
||||
}
|
||||
|
||||
enum SubscriptionError {
|
||||
SE_UNKNOWN = 0;
|
||||
SE_CODEC_UNSUPPORTED = 1;
|
||||
SE_TRACK_NOTFOUND = 2;
|
||||
}
|
||||
|
||||
enum AudioTrackFeature {
|
||||
TF_STEREO = 0;
|
||||
TF_NO_DTX = 1;
|
||||
TF_AUTO_GAIN_CONTROL = 2;
|
||||
TF_ECHO_CANCELLATION = 3;
|
||||
TF_NOISE_SUPPRESSION = 4;
|
||||
TF_ENHANCED_NOISE_CANCELLATION = 5;
|
||||
}
|
||||
|
||||
226
protobufs/livekit_room.proto
Normal file
226
protobufs/livekit_room.proto
Normal file
@@ -0,0 +1,226 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package livekit;
|
||||
option go_package = "github.com/livekit/protocol/livekit";
|
||||
option csharp_namespace = "LiveKit.Proto";
|
||||
option ruby_package = "LiveKit::Proto";
|
||||
|
||||
import "livekit_models.proto";
|
||||
import "livekit_egress.proto";
|
||||
import "livekit_agent.proto";
|
||||
|
||||
// Room service that can be performed on any node
|
||||
// they are Twirp-based HTTP req/responses
|
||||
service RoomService {
|
||||
// Creates a room with settings. Requires `roomCreate` permission.
|
||||
// This method is optional; rooms are automatically created when clients connect to them for the first time.
|
||||
rpc CreateRoom(CreateRoomRequest) returns (Room);
|
||||
|
||||
// List rooms that are active on the server. Requires `roomList` permission.
|
||||
rpc ListRooms(ListRoomsRequest) returns (ListRoomsResponse);
|
||||
|
||||
// Deletes an existing room by name or id. Requires `roomCreate` permission.
|
||||
// DeleteRoom will disconnect all participants that are currently in the room.
|
||||
rpc DeleteRoom(DeleteRoomRequest) returns (DeleteRoomResponse);
|
||||
|
||||
// Lists participants in a room, Requires `roomAdmin`
|
||||
rpc ListParticipants(ListParticipantsRequest) returns (ListParticipantsResponse);
|
||||
|
||||
// Get information on a specific participant, Requires `roomAdmin`
|
||||
rpc GetParticipant(RoomParticipantIdentity) returns (ParticipantInfo);
|
||||
|
||||
// Removes a participant from room. Requires `roomAdmin`
|
||||
rpc RemoveParticipant(RoomParticipantIdentity) returns (RemoveParticipantResponse);
|
||||
|
||||
// Mute/unmute a participant's track, Requires `roomAdmin`
|
||||
rpc MutePublishedTrack(MuteRoomTrackRequest) returns (MuteRoomTrackResponse);
|
||||
|
||||
// Update participant metadata, will cause updates to be broadcasted to everyone in the room. Requires `roomAdmin`
|
||||
rpc UpdateParticipant(UpdateParticipantRequest) returns (ParticipantInfo);
|
||||
|
||||
// Subscribes or unsubscribe a participant from tracks. Requires `roomAdmin`
|
||||
rpc UpdateSubscriptions(UpdateSubscriptionsRequest) returns (UpdateSubscriptionsResponse);
|
||||
|
||||
// Send data over data channel to participants in a room, Requires `roomAdmin`
|
||||
rpc SendData(SendDataRequest) returns (SendDataResponse);
|
||||
|
||||
// Update room metadata, will cause updates to be broadcasted to everyone in the room, Requires `roomAdmin`
|
||||
rpc UpdateRoomMetadata (UpdateRoomMetadataRequest) returns (Room);
|
||||
}
|
||||
|
||||
message CreateRoomRequest {
|
||||
// name of the room
|
||||
string name = 1;
|
||||
// configuration to use for this room parameters. Setting parameters below override the config defaults.
|
||||
string config_name = 12;
|
||||
// number of seconds to keep the room open if no one joins
|
||||
uint32 empty_timeout = 2;
|
||||
// number of seconds to keep the room open after everyone leaves
|
||||
uint32 departure_timeout = 10;
|
||||
// limit number of participants that can be in a room
|
||||
uint32 max_participants = 3;
|
||||
// override the node room is allocated to, for debugging
|
||||
string node_id = 4;
|
||||
// metadata of room
|
||||
string metadata = 5;
|
||||
// egress
|
||||
RoomEgress egress = 6;
|
||||
// agent
|
||||
RoomAgent agent = 11;
|
||||
// playout delay of subscriber
|
||||
uint32 min_playout_delay = 7;
|
||||
uint32 max_playout_delay = 8;
|
||||
// improves A/V sync when playout_delay set to a value larger than 200ms. It will disables transceiver re-use
|
||||
// so not recommended for rooms with frequent subscription changes
|
||||
bool sync_streams = 9;
|
||||
|
||||
// NEXT-ID: 13
|
||||
}
|
||||
|
||||
message RoomEgress {
|
||||
RoomCompositeEgressRequest room = 1;
|
||||
AutoParticipantEgress participant = 3;
|
||||
AutoTrackEgress tracks = 2;
|
||||
}
|
||||
|
||||
message RoomAgent {
|
||||
repeated CreateAgentJobDefinitionRequest agents = 1;
|
||||
}
|
||||
|
||||
|
||||
message ListRoomsRequest {
|
||||
// when set, will only return rooms with name match
|
||||
repeated string names = 1;
|
||||
}
|
||||
|
||||
message ListRoomsResponse {
|
||||
repeated Room rooms = 1;
|
||||
}
|
||||
|
||||
message DeleteRoomRequest {
|
||||
// name of the room
|
||||
string room = 1;
|
||||
}
|
||||
|
||||
message DeleteRoomResponse {
|
||||
}
|
||||
|
||||
message ListParticipantsRequest {
|
||||
// name of the room
|
||||
string room = 1;
|
||||
}
|
||||
|
||||
message ListParticipantsResponse {
|
||||
repeated ParticipantInfo participants = 1;
|
||||
}
|
||||
|
||||
message RoomParticipantIdentity {
|
||||
// name of the room
|
||||
string room = 1;
|
||||
// identity of the participant
|
||||
string identity = 2;
|
||||
}
|
||||
|
||||
message RemoveParticipantResponse {
|
||||
}
|
||||
|
||||
message MuteRoomTrackRequest {
|
||||
// name of the room
|
||||
string room = 1;
|
||||
string identity = 2;
|
||||
// sid of the track to mute
|
||||
string track_sid = 3;
|
||||
// set to true to mute, false to unmute
|
||||
bool muted = 4;
|
||||
}
|
||||
|
||||
message MuteRoomTrackResponse {
|
||||
TrackInfo track = 1;
|
||||
}
|
||||
|
||||
message UpdateParticipantRequest {
|
||||
string room = 1;
|
||||
string identity = 2;
|
||||
// metadata to update. skipping updates if left empty
|
||||
string metadata = 3;
|
||||
// set to update the participant's permissions
|
||||
ParticipantPermission permission = 4;
|
||||
// display name to update
|
||||
string name = 5;
|
||||
// attributes to update. it only updates attributes that have been set
|
||||
// to delete attributes, set the value to an empty string
|
||||
map<string, string> attributes = 6;
|
||||
}
|
||||
|
||||
message UpdateSubscriptionsRequest {
|
||||
string room = 1;
|
||||
string identity = 2;
|
||||
// list of sids of tracks
|
||||
repeated string track_sids = 3;
|
||||
// set to true to subscribe, false to unsubscribe from tracks
|
||||
bool subscribe = 4;
|
||||
// list of participants and their tracks
|
||||
repeated ParticipantTracks participant_tracks = 5;
|
||||
}
|
||||
|
||||
message UpdateSubscriptionsResponse {
|
||||
// empty for now
|
||||
}
|
||||
|
||||
message SendDataRequest {
|
||||
string room = 1;
|
||||
bytes data = 2;
|
||||
DataPacket.Kind kind = 3;
|
||||
// mark deprecated
|
||||
repeated string destination_sids = 4 [deprecated=true];
|
||||
// when set, only forward to these identities
|
||||
repeated string destination_identities = 6;
|
||||
optional string topic = 5;
|
||||
|
||||
// NEXT_ID: 7
|
||||
}
|
||||
|
||||
message SendDataResponse {
|
||||
//
|
||||
}
|
||||
|
||||
message UpdateRoomMetadataRequest {
|
||||
string room = 1;
|
||||
// metadata to update. skipping updates if left empty
|
||||
string metadata = 2;
|
||||
}
|
||||
|
||||
message RoomConfiguration {
|
||||
string name = 1; // Used as ID, must be unique
|
||||
// number of seconds to keep the room open if no one joins
|
||||
uint32 empty_timeout = 2;
|
||||
// number of seconds to keep the room open after everyone leaves
|
||||
uint32 departure_timeout = 3;
|
||||
// limit number of participants that can be in a room
|
||||
uint32 max_participants = 4;
|
||||
// egress
|
||||
RoomEgress egress = 5;
|
||||
// agent
|
||||
RoomAgent agent = 6;
|
||||
// playout delay of subscriber
|
||||
uint32 min_playout_delay = 7;
|
||||
uint32 max_playout_delay = 8;
|
||||
// improves A/V sync when playout_delay set to a value larger than 200ms. It will disables transceiver re-use
|
||||
// so not recommended for rooms with frequent subscription changes
|
||||
bool sync_streams = 9;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user