mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
Compare commits
1042 Commits
Author | SHA1 | Date | |
---|---|---|---|
2f252f67ba | |||
9e554ce527 | |||
6217f009fd | |||
46473126d2 | |||
b6ee6e9e22 | |||
1891f62c22 | |||
212d1cbfc2 | |||
73d93e476f | |||
80d82861ae | |||
8d034f53a4 | |||
6d3aa3a828 | |||
ed23aba0ac | |||
3847e5d6af | |||
f4465e2e9b | |||
fe2e87074a | |||
5b100d15d7 | |||
87cd36673e | |||
47b84f7316 | |||
a18da3e6cd | |||
6bb33fa18b | |||
59e6695a5d | |||
861cde7e61 | |||
e95d8d36aa | |||
04304ecf94 | |||
3989ea0780 | |||
e39585befc | |||
20fa14563c | |||
10910aa06d | |||
e6c2c90677 | |||
98712c7b71 | |||
2015d40186 | |||
a8aa34529e | |||
38cf0270b1 | |||
c8ca79f00b | |||
c02382b6e5 | |||
cb7c77d29e | |||
83e23c9c41 | |||
c6db90ae07 | |||
c2aa7263bd | |||
25be7ee14e | |||
332876495d | |||
6cbe50e3e7 | |||
599eaf85ce | |||
c03ca47e37 | |||
2246ce5142 | |||
79774921e4 | |||
ab453a0322 | |||
3725e400ce | |||
9859c7b25f | |||
056c1ef1fc | |||
41fbfb159a | |||
740a8a8d06 | |||
59ab9548c2 | |||
a1cf7f8d32 | |||
0d59983d1a | |||
488a57e2de | |||
dd9ad67e6b | |||
492e9d8df7 | |||
bc3b09dce4 | |||
5fd95ce374 | |||
77380a098e | |||
afc9c549d6 | |||
77f38bbf63 | |||
853eeb2e6b | |||
6ed6b4a679 | |||
99987f9373 | |||
b02239f3e3 | |||
bc436e9ec4 | |||
7e0874abb4 | |||
3d704702ba | |||
2735f80637 | |||
9275f7c2a9 | |||
229641aba0 | |||
ba143f2636 | |||
ffb34c3fd4 | |||
31c717eb40 | |||
7ae016bd3b | |||
501e0f63f5 | |||
a42398c457 | |||
d77e4ab10d | |||
8a8ea92140 | |||
adc46a80f9 | |||
3203f50a43 | |||
3dea5fd4f4 | |||
1024a2198a | |||
c8f0d5a3e4 | |||
3e55b3d7bc | |||
a93c4dfbbd | |||
aceb437c5f | |||
a137ccd8ba | |||
96bd4141fd | |||
e794762f72 | |||
9999b30d1a | |||
569e9681e5 | |||
9edc3eafa3 | |||
a1fcc35fd6 | |||
80d21ce80d | |||
08178b9354 | |||
60ce25c689 | |||
eaf34f3c3a | |||
ef0e907014 | |||
9fb0f56aa0 | |||
ae9ea8510c | |||
14409dec4f | |||
0cdea68567 | |||
542761e411 | |||
0dc8e070ff | |||
fe52cd8db1 | |||
d87a7e896b | |||
f7616ade1f | |||
f14379af68 | |||
a53df39e46 | |||
98187e2bd7 | |||
3ab7de99f3 | |||
1fef0691ed | |||
6b03efed42 | |||
eddd4abe76 | |||
e30dce8b21 | |||
6d12af3858 | |||
b9cbd0ecd1 | |||
3d7511ca75 | |||
fabf68b635 | |||
11326274e0 | |||
acd3940a7b | |||
8ba441da7f | |||
5d9d5e0746 | |||
7a31e88ed6 | |||
d9f35a855d | |||
9678d59bbe | |||
0be5564ce7 | |||
8a90051ab5 | |||
02c7a7f7c7 | |||
e7c8c1d6b6 | |||
449ad6d13c | |||
ae4ee0332c | |||
4a4c97aac4 | |||
4491da0350 | |||
32960afc1b | |||
46f624e3b9 | |||
9daae857de | |||
bac5b1551b | |||
f61f45fa23 | |||
fbc6e02ff6 | |||
56a17a9131 | |||
a148f7fd7c | |||
0dd0d01e8f | |||
bcd16d791c | |||
539e636939 | |||
7f1c4a1c4c | |||
6b254e0ad5 | |||
f36185a964 | |||
588487f860 | |||
f3ab2100f0 | |||
f96b40bbf8 | |||
9679de097f | |||
a51ad1383b | |||
1dd96fa714 | |||
8285805108 | |||
d91b3f8dd9 | |||
6734c928a2 | |||
58448f363b | |||
2e32b7e299 | |||
a651a1a69f | |||
c368696ddc | |||
94269c0640 | |||
5da6588ee6 | |||
ac9c6b4a1f | |||
aa8fa00811 | |||
9c4bd7a487 | |||
eb00294bbb | |||
04c60fd1ac | |||
80de8ada57 | |||
9cdb32d56a | |||
31a505f4a4 | |||
bfa6cd0e19 | |||
c51ecda6b4 | |||
471d44ae11 | |||
513bd1372b | |||
355cee0db9 | |||
38936173d1 | |||
eea56b4c71 | |||
802cc3a48f | |||
ba4720012d | |||
4f83b73732 | |||
68db13fc24 | |||
2d4dd580b4 | |||
d03e94ada8 | |||
91126a59c2 | |||
bd18aee43c | |||
4ead1c3de5 | |||
f3b2a6eba2 | |||
c4d9a27d2f | |||
fdcba2734d | |||
981ba7b28f | |||
822a1751a2 | |||
a4279d09a6 | |||
14d43ac05b | |||
5e6c92ab16 | |||
9ced745258 | |||
948f6ccd95 | |||
1c58727ca9 | |||
612bd9960c | |||
1474499886 | |||
cf99c68843 | |||
d2865b9f4f | |||
865c0c79db | |||
3462e7f50e | |||
37e412e710 | |||
5822992b44 | |||
eb7787fb7d | |||
f1371034f7 | |||
5d12dfa368 | |||
c4282011fa | |||
9ddc22fa94 | |||
517788e86a | |||
ed29ec528d | |||
481b26d09d | |||
da66ad6d41 | |||
1f75b305c3 | |||
0802d03d74 | |||
3970674003 | |||
b00a93dd6b | |||
19b33a8558 | |||
7918438747 | |||
31a085db73 | |||
72997eaa85 | |||
7bb91da1d0 | |||
e3e62c3903 | |||
631ed022ed | |||
590943ed95 | |||
e9c17c9a1d | |||
f8c8e42ae9 | |||
0b0560019a | |||
133d3fdda7 | |||
53e98dbe15 | |||
0eaa9187ea | |||
344f5bda69 | |||
33b080b3b8 | |||
1c85894472 | |||
ba4e5959b1 | |||
5534f9a248 | |||
eb206549ff | |||
0852f32b05 | |||
a1ea84b286 | |||
baba8790bc | |||
1da7f47af9 | |||
60d9b72fea | |||
7a7a8b7ed1 | |||
100565081c | |||
27f82434b6 | |||
353a9aa671 | |||
00da210365 | |||
bce6c86e60 | |||
bed6a1b1e2 | |||
497443f012 | |||
75a06afab0 | |||
121d9f4920 | |||
56371a7d80 | |||
2810787156 | |||
6b53cb59a5 | |||
64062cc879 | |||
7ca902e39a | |||
10ed2738f5 | |||
71392613b2 | |||
4eb7bed2ff | |||
ff21f5b357 | |||
98db248776 | |||
ed4872b94c | |||
8c5c6958cf | |||
f9c81f99f2 | |||
acfdb26135 | |||
37cf8e9d29 | |||
1c6670c9b0 | |||
88c72cd80a | |||
728ea16701 | |||
a3bc9f768a | |||
8d88bc19ed | |||
333ffa0e89 | |||
4ded810ba9 | |||
b2aa54f3f8 | |||
97836cc5eb | |||
b1df0dca97 | |||
4a6816575a | |||
31e2937b8d | |||
0f434004a8 | |||
ba75c45cee | |||
47492c3fa2 | |||
819917c4bf | |||
1ce0fd643c | |||
a7d02a79a9 | |||
645cbf9888 | |||
b2d39ab2d7 | |||
5843521cf1 | |||
4fbc45b40b | |||
2719da3685 | |||
f61bc809dd | |||
19f9593ac1 | |||
a479f529af | |||
76635ff31b | |||
e277cae799 | |||
05baf6b8ac | |||
758441d65d | |||
631a008fa0 | |||
71391914ae | |||
54e79936e5 | |||
a833822eae | |||
cfa8d8d180 | |||
b692756260 | |||
dbc62ad63a | |||
2ac97dea35 | |||
9451814299 | |||
5e01283018 | |||
f7512c3cc0 | |||
f4997375e9 | |||
fbad54795e | |||
2ca85d05f0 | |||
67e89e79be | |||
e4676668bb | |||
3604995c2f | |||
8b731f3ba4 | |||
83f702fbab | |||
fbebf1c7d3 | |||
be14947668 | |||
0fcf770043 | |||
51ec3ede1f | |||
4e2302936f | |||
a6c6a42669 | |||
c486bc64c4 | |||
3709ea1a95 | |||
be7fa79327 | |||
c1660f8ca2 | |||
5e41eaf3dd | |||
898e761988 | |||
846d52ebe5 | |||
77ac6a7a26 | |||
93c2dab634 | |||
f23ba0c513 | |||
6a733bbb13 | |||
297d920fdc | |||
c741a73893 | |||
540e1f562f | |||
bfcd16ea28 | |||
3a8703de87 | |||
247ca71bf9 | |||
88d39ab47a | |||
a5af45fb31 | |||
dc04cb54ab | |||
c9fa82f073 | |||
c418cbf4ef | |||
3450c6b9a3 | |||
1ada33a4f0 | |||
a669852694 | |||
a5b6ea6c4c | |||
fe217ef5df | |||
e0a25dbf48 | |||
86191aff7f | |||
6bed39e4fa | |||
2a7aa432a9 | |||
4e19e41460 | |||
061f5d5099 | |||
796bdebd90 | |||
010102da25 | |||
d8f2aeb004 | |||
5173338949 | |||
a8b00b026e | |||
d4db0bdfe5 | |||
b26cd901ca | |||
1e2fc783e5 | |||
8667333416 | |||
5088d7dd86 | |||
e78f26e9c0 | |||
be10227829 | |||
ef47c172c5 | |||
053faa1661 | |||
6d63779c70 | |||
62ad1a483c | |||
f4f760a231 | |||
0eaef1ac3b | |||
27245cca1a | |||
afcc11af57 | |||
9fc41e4245 | |||
a0e0910117 | |||
8a7d7a41a4 | |||
45d5f4a760 | |||
101b795c0a | |||
8311bd3fd9 | |||
ec1926f7a6 | |||
e1c92956f8 | |||
9233d4f6dc | |||
f2f5661625 | |||
4338cf57e6 | |||
a0dce77a2f | |||
ca93882870 | |||
22fbb4262c | |||
2845fd07a7 | |||
c02f40beb6 | |||
79a7b444a2 | |||
9c11bbc7a1 | |||
e1cb57563a | |||
1057d765f7 | |||
30db1a8a63 | |||
c7b49b28c2 | |||
40ea7cfe15 | |||
b3b2ae267a | |||
88176f4d2b | |||
6917ac48a3 | |||
c326d65ad3 | |||
e1a2f0c0f4 | |||
6953bc6105 | |||
209555d36d | |||
e65958f33e | |||
02fea8938a | |||
f5277e4931 | |||
d78ed32df5 | |||
8447395482 | |||
915a7b6e15 | |||
b70c39789e | |||
865cb4857b | |||
774abd66fa | |||
e52d86e6f9 | |||
3c0b5b0b43 | |||
46068573c5 | |||
06c29b7810 | |||
631452567d | |||
6c34f00cfe | |||
208c7f18b2 | |||
5e393ed3c4 | |||
8b16abd370 | |||
94c232d78e | |||
4b41c52522 | |||
6bfe533a20 | |||
f0b8aad4b1 | |||
796cf36a7f | |||
7706d66754 | |||
d992536914 | |||
286b76672c | |||
f0d1b5a1e4 | |||
71ad8930d8 | |||
0f6cbb0c5c | |||
78584b3194 | |||
c0eb4d652b | |||
edbb28038f | |||
3b7e9b4eba | |||
6d0d07abfe | |||
e9f90b4990 | |||
d25b65c124 | |||
5864864123 | |||
2f244ae37e | |||
f83317f7c8 | |||
973b4b9f70 | |||
9389ceaf4f | |||
928dc30810 | |||
c9d6d10995 | |||
9323e1cf59 | |||
93a456ca82 | |||
f6693d5587 | |||
fd09a0ce54 | |||
50d3a2600e | |||
7e1b05933f | |||
03c4fdc607 | |||
0485cf51ff | |||
ba9201831b | |||
f660036a25 | |||
f09cbfddbc | |||
a12f1dcb13 | |||
d2bb1ddc29 | |||
528d81106e | |||
f0bed24742 | |||
5b54d8135e | |||
31559051f6 | |||
e6dbc9bfdf | |||
d1ee9d83b5 | |||
ffa6371e3d | |||
ee6e241144 | |||
bdf1023b16 | |||
9a8f248a75 | |||
6845cda146 | |||
4488b2b7a5 | |||
7dd8cb5d8d | |||
ebf631a15f | |||
3770b75a78 | |||
5d81b61325 | |||
a8e5171b40 | |||
a3e0abb6ce | |||
c51f20eb99 | |||
be897c4df2 | |||
11b948fb69 | |||
070660848b | |||
61af0ec9c6 | |||
897f115363 | |||
b8fcf0355c | |||
5bdff87e3f | |||
31991a3567 | |||
25c369d422 | |||
8d396b1518 | |||
ba327b746a | |||
7d11f912c9 | |||
919cdfb5a9 | |||
da9dd6f775 | |||
8b841f026e | |||
5342b39640 | |||
fc08add504 | |||
20379ac61e | |||
af40aa59ab | |||
f9afc5597a | |||
40178e4661 | |||
ad56abd6f5 | |||
9073a09d84 | |||
2a4e6fce3f | |||
0a3a407217 | |||
9ca9cf80df | |||
6c881a5da7 | |||
5322acd58c | |||
ce489e53c6 | |||
0c95e509dc | |||
46a006ed54 | |||
0cc9378d05 | |||
f3edb2ee68 | |||
a539f23194 | |||
6b6dec79c5 | |||
d7f5c042bf | |||
2fe9812b26 | |||
1c15c3c7a3 | |||
1ee5857139 | |||
2f9c2b0d65 | |||
6e5161f43b | |||
d492d74a64 | |||
26506731c5 | |||
b0c03d4f47 | |||
3bc8348c32 | |||
a034fbba83 | |||
73b5261c49 | |||
cee0cbebc2 | |||
9723147429 | |||
dca385ae87 | |||
71f792944c | |||
6041c4acd2 | |||
0c7529705e | |||
63f1ea1cec | |||
4bbaa75f41 | |||
5fe9314b74 | |||
bfd26493ab | |||
5d74d5d03e | |||
3142e097a5 | |||
b25d8d8201 | |||
7e68cd016a | |||
9e4fc9eb73 | |||
fb0d13a171 | |||
f097b36c2e | |||
57ce3cf80b | |||
c1da62391f | |||
d8882ce979 | |||
9c1871494e | |||
ac26c9114a | |||
fb0a57003c | |||
cda22233a6 | |||
add307577d | |||
4af46ac2b9 | |||
8d39752bda | |||
e3ff9c013e | |||
635870ba4b | |||
a827afb05b | |||
0195e13bdc | |||
76d8b688fd | |||
b4857e2c79 | |||
ac2ae90e8a | |||
0b5cb76b5d | |||
6eed9606ae | |||
29679ff94c | |||
65a1f8b746 | |||
48182d35bd | |||
b526ed47be | |||
2225dec2a9 | |||
011458d642 | |||
e32fff3f61 | |||
0b42e3d0f3 | |||
2b746d1353 | |||
6fed6f9f7d | |||
480945073a | |||
ef6df94838 | |||
fb05848426 | |||
e5be6b96fb | |||
2d706a245a | |||
1abc4f491b | |||
b21a45b43e | |||
585f8450e1 | |||
d3401db5fe | |||
45dc6447bf | |||
409c270ba2 | |||
aed0234d47 | |||
07ebbe56e6 | |||
47b485819f | |||
fd32f7f435 | |||
c4b07b1a7b | |||
95bbeb103e | |||
bfb5570b7a | |||
1e19cf7ccc | |||
0d8999d64f | |||
91fde777cf | |||
061ab12b10 | |||
c516e87180 | |||
de6bfdca0a | |||
788f70d5f3 | |||
b0a35789f6 | |||
eb7fbf011f | |||
50bb6e7204 | |||
15610bb296 | |||
ae0ffdc4d5 | |||
033a6929c3 | |||
7c8292a88d | |||
ab5dad7d91 | |||
0744d215c6 | |||
2622eb4663 | |||
74b3dc2831 | |||
149ddfa8f8 | |||
3830870a83 | |||
b94be080f6 | |||
83382d8dcf | |||
f815228677 | |||
d7d8d23de7 | |||
5834c6ed54 | |||
b0512b3ba7 | |||
d3a7a6ef55 | |||
99fad5bc83 | |||
a54171e1cd | |||
84629f6e63 | |||
b18d597bdc | |||
9125dc4cce | |||
b0e3ea8765 | |||
7c457546f1 | |||
83bef1a840 | |||
b4d89d5666 | |||
3ba8a77d9a | |||
82c31dc47a | |||
7fce694577 | |||
e352d9750d | |||
3d9eac8e6d | |||
6c85b33539 | |||
d979ada4fe | |||
faeeae17d1 | |||
82d1a2b4f9 | |||
cac3496145 | |||
53bfa077ae | |||
0b9366fa28 | |||
c95b709f39 | |||
5745da5466 | |||
ef853e34d6 | |||
7177913d9b | |||
86b6ddb625 | |||
53b9a46feb | |||
d46c1f8687 | |||
c92477b457 | |||
059244bb32 | |||
f10c0d2558 | |||
f5ae89500f | |||
2e3a24d32e | |||
f955d81c5e | |||
4f1fa3bc32 | |||
8305d907e3 | |||
ab73d61178 | |||
8f88d1b7c4 | |||
c5cdbc48a9 | |||
fe00f31147 | |||
79ddb43ac3 | |||
89f95f6459 | |||
87c13b6f26 | |||
d16ea653fe | |||
51dc7fceb0 | |||
bc1c6f7b9a | |||
3fdf77d29b | |||
0b97502029 | |||
cba2bf5cb8 | |||
d8b37328a1 | |||
bb9cf83744 | |||
56fc6ae47c | |||
0cf17cf3db | |||
1bd3297055 | |||
6a10662bc4 | |||
2d84ad8963 | |||
1ec69cbc0d | |||
0057744e57 | |||
45892b4347 | |||
3d41e8882a | |||
436b8216ec | |||
03ba2a680e | |||
2821962d12 | |||
43d38a2e63 | |||
ea28d217e0 | |||
89fe6d57c9 | |||
1d0e1143bf | |||
1a99353559 | |||
ad97d04e37 | |||
25c3abe873 | |||
8eeea50ede | |||
0ded11c6a2 | |||
b6c542212d | |||
182bde4884 | |||
eb84f677cf | |||
3ecb637e70 | |||
bfc826b898 | |||
efc6d4c15a | |||
a96da35f11 | |||
2bff3798af | |||
0fb50a3273 | |||
1c17eee125 | |||
14a43a9cd2 | |||
7bbc7366f2 | |||
879bedd781 | |||
8b79bfab4f | |||
f022e09938 | |||
a63b6a0e3d | |||
dd1facec06 | |||
a46b5716be | |||
e96b7bfd62 | |||
a60c1c1365 | |||
4c39fcd614 | |||
e6b341f2a0 | |||
bdf9e76a6b | |||
e0fc395fbe | |||
107c03653d | |||
a5058cf951 | |||
ed2726c9d2 | |||
6b31ff7e79 | |||
86d3925bf4 | |||
8c2cde4c13 | |||
2d6e34ee6d | |||
adb5577b01 | |||
84ef3f223d | |||
fa50008f3d | |||
6a5537f90f | |||
e0d33a9683 | |||
4092c9a473 | |||
fcaac3d515 | |||
e86d1aad02 | |||
2c94b4c332 | |||
2b6933f6e2 | |||
2e2a7f073e | |||
515c7852ba | |||
5577277944 | |||
ed9e4ff168 | |||
d10915c7a8 | |||
40763fc15d | |||
88d3271c40 | |||
21940c922d | |||
93adc8587d | |||
1907f8d1d1 | |||
ca8848827e | |||
3a7473ba91 | |||
a99da27ff6 | |||
c476649f3e | |||
062473d6f4 | |||
d0ed43a8e5 | |||
701098d19d | |||
c33a0ab439 | |||
b8af848d3a | |||
3dd7fe5d4a | |||
81ab199308 | |||
92938d2c35 | |||
c914632663 | |||
c95104fada | |||
2209fe30e2 | |||
979e0ddc88 | |||
9fab714674 | |||
2eb6463ab0 | |||
5e4d6fbd3f | |||
30df5f3558 | |||
252dd7e09f | |||
2915690d22 | |||
1993596232 | |||
8946e8997f | |||
dfeb156da9 | |||
c10c35e38e | |||
67bfdde113 | |||
c62178a7fe | |||
c12a4323e7 | |||
fd1c4abad7 | |||
bd4fe5a1a7 | |||
190f5ebfc4 | |||
79493df32e | |||
71d523437e | |||
268f503875 | |||
5ac47b823f | |||
ec572da822 | |||
37f96b8cf2 | |||
545db60b98 | |||
f65fdcdbc1 | |||
579acabe5e | |||
f2a9ff8551 | |||
2e40e07563 | |||
cb3af837c6 | |||
3cce89ea3f | |||
5f2dfb24ca | |||
4bc02a7389 | |||
eeeca8afd0 | |||
4f607df5fc | |||
e06b3e2052 | |||
b14b18e4be | |||
fb616b4b53 | |||
a1c5bc00bc | |||
0921632f87 | |||
38ad465233 | |||
881de01073 | |||
2d973e0b90 | |||
e9b43b9b2a | |||
b9193989b0 | |||
be6d9791f5 | |||
baac1b1d80 | |||
40e2d410dd | |||
11617eea99 | |||
5748c4d0ec | |||
2e5b903eae | |||
9405b17e14 | |||
7d1f0e2a69 | |||
c245c24752 | |||
5b0410a207 | |||
62e4c42aa6 | |||
fe1b14ff57 | |||
c074088f2f | |||
0391280c13 | |||
4f98b9e41b | |||
a8de9ac472 | |||
ec7f3fa057 | |||
5cfefd8b15 | |||
7e6b53311d | |||
16bc68f2f9 | |||
974d6b48b2 | |||
db2b1e2dc7 | |||
98656b5d2f | |||
3c7570d814 | |||
fc3e30a826 | |||
2f0476b43c | |||
e310c7d744 | |||
8a649b89c8 | |||
95f52987ef | |||
82b8c66d51 | |||
83fb1843ee | |||
58b10069ab | |||
9cda739672 | |||
276bba050b | |||
147e49b362 | |||
bc338c1f4a | |||
e8fbb18a71 | |||
682c349831 | |||
14b311f6ab | |||
8a40f355c8 | |||
ae2f90c5c2 | |||
7aff773e2c | |||
0cdfa6e7f6 | |||
fc637eef6d | |||
b4c3141170 | |||
41257f7af5 | |||
b204f3ec90 | |||
b4926b3535 | |||
77d63e9848 | |||
94dcd58c2e | |||
689ce16f1b | |||
edc64b8336 | |||
03db5bfd8d | |||
9ad340ab02 | |||
962e26040d | |||
b07884c1da | |||
bad0fb62ed | |||
28e522ce97 | |||
c206cdfa4c | |||
c31ec077f5 | |||
afc6a60746 | |||
0a495b67e6 | |||
37ea7073d5 | |||
feaeef5a70 | |||
5586670d38 | |||
65a9139ffe | |||
3d76f078cd | |||
a1de1b11bc | |||
b5a3e3a4f0 | |||
d7b0ad4916 | |||
2a80a6b217 | |||
9df72f54d5 | |||
ef75ca36c9 | |||
84c0b698f5 | |||
3d9a4ef1e6 | |||
cf51fdceef | |||
85a52ab01f | |||
f2792c0b40 | |||
97109087a4 | |||
953f07f21f | |||
03f1035690 | |||
a561c60f7e | |||
7963b328f9 | |||
3b7e216409 | |||
9ae43a6f75 | |||
6b86de1fb9 | |||
4e6d4ac437 | |||
3b197651cc | |||
c675f1c20c | |||
e87955d59a | |||
c718d8d803 | |||
454a68d1b7 | |||
45f6f74cbe | |||
cb7412a457 | |||
a9fc82365c | |||
edc0fed9e2 | |||
1c718963ea | |||
cd40ccdb9d | |||
80e1dc2446 | |||
d03c4cc4b9 | |||
7bd434e755 | |||
640bcb90c6 | |||
08e86a1378 | |||
fefcc3937a | |||
25210dfa52 | |||
56fbb7b9cf | |||
c55d33b956 | |||
0c5bce101e | |||
0a50e2a95c | |||
5ad940924b | |||
6eb49930bf | |||
3a0d5fb190 | |||
8c2eee2e25 | |||
2e19c5f08a | |||
548b53437f | |||
e1ca9a8029 | |||
389ef2aea9 | |||
357691bad5 | |||
beadb56b05 | |||
745fb5ea29 | |||
58434cac3b | |||
c34dff17ff | |||
73f00ca195 | |||
40503b4212 | |||
4ada388828 | |||
e2d261259d | |||
677e393f47 | |||
e5dae8f5a7 | |||
650de119a4 | |||
6d7975afeb | |||
811e3f8cc8 | |||
4f3be34db1 | |||
b160fd2320 | |||
002bf08b97 | |||
a6bab968f5 | |||
408b336057 | |||
7e5716185e | |||
6d47bd6477 | |||
989c8b1857 | |||
06e9e0afab | |||
d75523c111 | |||
dacec803c6 | |||
0ae4416242 | |||
079d7eb6ca | |||
b764b4d0e5 | |||
2c2d61d908 | |||
6a323b9371 | |||
d76ff16162 | |||
952f3a586a | |||
3ecb9702ef | |||
563936ea08 | |||
13cd2704ae | |||
ae83d9dca1 | |||
66a059ecdf | |||
53936a4f76 | |||
488a095fdf | |||
1878de9f6b | |||
2a2f3441ef | |||
53c939a97f | |||
8d7ed32fc2 | |||
a1fa5dc3cb | |||
4b9a84ccee | |||
650957b6d1 | |||
6e21d041fe | |||
e2b70fd795 | |||
2d49bcc437 | |||
8b3dce3256 | |||
8cf6a1e72c | |||
a3ce7197ac | |||
5da1e55f8e | |||
661fd4efa8 | |||
047e6e11bf | |||
db8bc1af2d | |||
1386e4f91c | |||
11e2717809 | |||
57bc0a2b95 | |||
8efb30c4ee | |||
969feefcc5 | |||
db1527ab9b | |||
e88a60fa50 | |||
7ab3e38da7 | |||
7b0b836809 | |||
c8e7cfcd7b | |||
c516c89c97 | |||
c2937d7857 | |||
fe644cfa82 | |||
e84e5388a5 | |||
5e11d0ea13 | |||
a3ecb6e0e9 | |||
1d30f13fd8 | |||
b9ae28483c | |||
6a6d316e09 | |||
22d2ee6bbd | |||
68e55613fc | |||
1d44d3a109 | |||
11a641cc0d | |||
7570fbf7a4 | |||
67f7e28867 | |||
9b2d30b4d5 | |||
399815525f | |||
191cfa08d5 | |||
fcf1fa8aff | |||
73302cb060 | |||
70a8533f5e | |||
fe724db12d | |||
096a8ec6ba | |||
cc5f9c9aa7 | |||
3e14b41600 | |||
ccc2bd8667 | |||
45f86b17f1 | |||
90aebecc5b | |||
b0170ef671 | |||
d418b4e624 | |||
47505547af | |||
de2e73c9d4 | |||
ab1a43163b | |||
2556dd320f | |||
2120381c0e | |||
dc06900f7f | |||
7571dd5042 | |||
d6caa872b8 | |||
2e9829ddd1 | |||
9621ea90f7 | |||
f58da1254b | |||
a6ba7f8feb | |||
9c89f12275 | |||
41181f7260 | |||
7251862ecf | |||
c0512d5b5f | |||
b9731dff21 | |||
3981abc5ca | |||
6aef437f58 | |||
9b7752896a | |||
d074027610 | |||
b137148186 | |||
e1f6260034 | |||
b24cbaa904 |
10
.editorconfig
Normal file
10
.editorconfig
Normal file
@ -0,0 +1,10 @@
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{c,cpp,h,hpp}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
22
.github/CONTRIBUTING.md
vendored
22
.github/CONTRIBUTING.md
vendored
@ -1,22 +0,0 @@
|
||||
## Contributing to obs-websocket
|
||||
|
||||
### Translating obs-websocket to your language
|
||||
Localization happens on Crowdin: https://crowdin.com/project/obs-websocket
|
||||
|
||||
### Writing code for obs-websocket
|
||||
#### Coding Guidelines
|
||||
- Function and variable names: snake_case for C names, CamelCase for C++ names
|
||||
- Tabs are 8 columns wide
|
||||
- 80 columns max.
|
||||
|
||||
#### Commit Guidelines
|
||||
- Commits follow the 50/72 standard:
|
||||
- 50 characters max for the title
|
||||
- One empty line after the title
|
||||
- Description wrapped to 72 columns max per line.
|
||||
- Commit titles:
|
||||
- Use present tense
|
||||
- Prefix the title with a "scope" name
|
||||
- e.g: "CI: fix wrong behaviour when packaging for OS X"
|
||||
- Typical scopes: CI, General, Request, Event, Server
|
||||
- Look at existing commits for more examples
|
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
open_collective: obs-websocket
|
||||
github: Palakis
|
||||
custom: https://www.paypal.me/stephanelepin
|
14
.github/ISSUE_TEMPLATE.md
vendored
14
.github/ISSUE_TEMPLATE.md
vendored
@ -1,16 +1,20 @@
|
||||
##### Issue type
|
||||
Bug report? Feature request? Other?
|
||||
<!--- Uncomment one of the two options below. -->
|
||||
|
||||
<!--- - Bug report -->
|
||||
<!--- - Feature request -->
|
||||
|
||||
##### Description
|
||||
*Replace this with a description of the bug encountered or feature requested.*
|
||||
<!--- Describe the bug encountered or feature requested. -->
|
||||
|
||||
##### Steps to reproduce and other useful info
|
||||
*If it's a bug, please describe the steps to reproduce it and PLEASE include an OBS log file. Otherwise, remove this section.*
|
||||
<!--- If it's a bug, please describe the steps to reproduce it and PLEASE include an OBS log file. Otherwise, remove this section. -->
|
||||
|
||||
##### Technical information
|
||||
- **Operating System** :
|
||||
- **OBS Studio version** :
|
||||
- **obs-websocket version** :
|
||||
|
||||
##### Development Environment
|
||||
*If you're trying to compile obs-websocket, please describe your compiler type and version (e.g: GCC 4.7, VC2013, ...), and the CMake settings used.
|
||||
Remove this section if it doesn't apply to your case.*
|
||||
<!--- If you're trying to compile obs-websocket, please describe your compiler type and version (e.g: GCC 4.7, VC2013, ...), and the CMake settings used. -->
|
||||
<!--- Remove this section if it does not apply. -->
|
||||
|
BIN
.github/images/obsws_logo.png
vendored
Normal file
BIN
.github/images/obsws_logo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
35
.github/pull_request_template.md
vendored
Normal file
35
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
<!--- Please fill out the following template, which will help other contributors review your Pull Request. -->
|
||||
|
||||
<!--- Make sure you’ve read the contribution guidelines here: https://github.com/Palakis/obs-websocket/blob/4.x-current/CONTRIBUTING.md -->
|
||||
|
||||
### Description
|
||||
<!--- Describe your changes. -->
|
||||
|
||||
### Motivation and Context
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
<!--- If it fixes/closes an open issue or implements feature request, -->
|
||||
<!--- please link to the issue here. -->
|
||||
|
||||
### How Has This Been Tested?
|
||||
<!--- Please describe in detail how you tested your changes, along with the OS(s) you tested with. -->
|
||||
Tested OS(s):
|
||||
|
||||
### Types of changes
|
||||
<!--- What types of changes does your PR introduce? Uncomment all that apply -->
|
||||
|
||||
<!--- - Bug fix (non-breaking change which fixes an issue) -->
|
||||
<!--- - New request/event (non-breaking) -->
|
||||
<!--- - Documentation change (a change to documentation pages) -->
|
||||
<!--- - Enhancement (modification to a current event/request which adds functionality) -->
|
||||
<!--- - Performance enhancement (non-breaking change which improves efficiency) -->
|
||||
<!--- - Code cleanup (non-breaking change which makes code smaller or more readable) -->
|
||||
|
||||
### Checklist:
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
- [ ] I have read the [**contributing** document](https://github.com/Palakis/obs-websocket/blob/4.x-current/CONTRIBUTING.md).
|
||||
- [ ] My code is not on the master branch.
|
||||
- [ ] The code has been tested.
|
||||
- [ ] All commit messages are properly formatted and commits squashed where appropriate.
|
||||
- [ ] I have included updates to all appropriate documentation.
|
||||
|
412
.github/workflows/pr_push.yml
vendored
Normal file
412
.github/workflows/pr_push.yml
vendored
Normal file
@ -0,0 +1,412 @@
|
||||
name: 'CI Multiplatform Build'
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
branches:
|
||||
- 4.x-current
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
branches:
|
||||
- 4.x-current
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
name: 'Windows 32+64bit'
|
||||
runs-on: [windows-latest]
|
||||
if: contains(github.event.head_commit.message, '[skip ci]') != true
|
||||
env:
|
||||
QT_VERSION: '5.10.1'
|
||||
WINDOWS_DEPS_VERSION: '2017'
|
||||
CMAKE_GENERATOR: "Visual Studio 16 2019"
|
||||
CMAKE_SYSTEM_VERSION: "10.0"
|
||||
steps:
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v1.0.0
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ${{ github.workspace }}/obs-websocket
|
||||
submodules: 'recursive'
|
||||
- name: 'Checkout OBS'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: obsproject/obs-studio
|
||||
path: ${{ github.workspace }}/obs-studio
|
||||
submodules: 'recursive'
|
||||
- name: 'Get OBS-Studio git info'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
echo ::set-env name=OBS_GIT_BRANCH::$(git rev-parse --abbrev-ref HEAD)
|
||||
echo ::set-env name=OBS_GIT_HASH::$(git rev-parse --short HEAD)
|
||||
echo ::set-env name=OBS_GIT_TAG::$(git describe --tags --abbrev=0)
|
||||
- name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
git checkout ${{ env.OBS_GIT_TAG }}
|
||||
git submodule update
|
||||
- name: 'Get obs-websocket git info'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
echo ::set-env name=GIT_BRANCH::${{ github.event.pull_request.head.ref }}
|
||||
echo ::set-env name=GIT_HASH::$(git rev-parse --short HEAD)
|
||||
echo ::set-env name=GIT_TAG::$(git describe --tags --abbrev=0)
|
||||
- name: 'Install prerequisite: QT'
|
||||
run: |
|
||||
curl -kLO https://cdn-fastly.obsproject.com/downloads/Qt_${{ env.QT_VERSION }}.7z -f --retry 5 -C -
|
||||
7z x Qt_${{ env.QT_VERSION }}.7z -o"${{ github.workspace }}\cmbuild\QT"
|
||||
- name: 'Install prerequisite: Pre-built OBS dependencies'
|
||||
run: |
|
||||
curl -kLO https://cdn-fastly.obsproject.com/downloads/dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -f --retry 5 -C -
|
||||
7z x dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -o"${{ github.workspace }}\cmbuild\deps"
|
||||
- name: 'Restore OBS 32-bit build v${{ env.OBS_GIT_TAG }} from cache'
|
||||
id: build-cache-obs-32
|
||||
uses: actions/cache@v1
|
||||
env:
|
||||
CACHE_NAME: 'build-cache-obs-32'
|
||||
with:
|
||||
path: ${{ github.workspace }}/obs-studio/build32
|
||||
key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ env.CACHE_NAME }}-
|
||||
- name: 'Configure OBS 32-bit'
|
||||
if: steps.build-cache-obs-32.outputs.cache-hit != 'true'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
mkdir .\build32
|
||||
cd .\build32
|
||||
cmake -G "${{ env.CMAKE_GENERATOR }}" -A Win32 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2017" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win32" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES ..
|
||||
- name: 'Build OBS-Studio 32-bit'
|
||||
if: steps.build-cache-obs-32.outputs.cache-hit != 'true'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
msbuild /m /p:Configuration=RelWithDebInfo .\build32\libobs\libobs.vcxproj
|
||||
msbuild /m /p:Configuration=RelWithDebInfo .\build32\UI\obs-frontend-api\obs-frontend-api.vcxproj
|
||||
- name: 'Restore OBS 64-bit build v${{ env.OBS_GIT_TAG }} from cache'
|
||||
id: build-cache-obs-64
|
||||
uses: actions/cache@v1
|
||||
env:
|
||||
CACHE_NAME: 'build-cache-obs-64'
|
||||
with:
|
||||
path: ${{ github.workspace }}/obs-studio/build64
|
||||
key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ env.CACHE_NAME }}-
|
||||
- name: 'Configure OBS 64-bit'
|
||||
if: steps.build-cache-obs-64.outputs.cache-hit != 'true'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
mkdir .\build64
|
||||
cd .\build64
|
||||
cmake -G "${{ env.CMAKE_GENERATOR }}" -A x64 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2017_64" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win64" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES ..
|
||||
- name: 'Build OBS-Studio 64-bit'
|
||||
if: steps.build-cache-obs-64.outputs.cache-hit != 'true'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
msbuild /m /p:Configuration=RelWithDebInfo .\build64\libobs\libobs.vcxproj
|
||||
msbuild /m /p:Configuration=RelWithDebInfo .\build64\UI\obs-frontend-api\obs-frontend-api.vcxproj
|
||||
- name: 'Configure obs-websocket 64-bit'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: |
|
||||
mkdir .\build64
|
||||
cd .\build64
|
||||
cmake -G "${{ env.CMAKE_GENERATOR }}" -A x64 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2017_64" -DLibObs_DIR="${{ github.workspace }}\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="${{ github.workspace }}\obs-studio\libobs" -DLIBOBS_LIB="${{ github.workspace }}\obs-studio\build64\libobs\RelWithDebInfo\obs.lib" -DOBS_FRONTEND_LIB="${{ github.workspace }}\obs-studio\build64\UI\obs-frontend-api\RelWithDebInfo\obs-frontend-api.lib" ..
|
||||
- name: 'Configure obs-websocket 32-bit'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: |
|
||||
mkdir .\build32
|
||||
cd .\build32
|
||||
cmake -G "${{ env.CMAKE_GENERATOR }}" -A Win32 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2017" -DLibObs_DIR="${{ github.workspace }}\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="${{ github.workspace }}\obs-studio\libobs" -DLIBOBS_LIB="${{ github.workspace }}\obs-studio\build32\libobs\RelWithDebInfo\obs.lib" -DOBS_FRONTEND_LIB="${{ github.workspace }}\obs-studio\build32\UI\obs-frontend-api\RelWithDebInfo\obs-frontend-api.lib" ..
|
||||
- name: 'Build obs-websocket 64-bit'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: msbuild /m /p:Configuration=RelWithDebInfo .\build64\obs-websocket.sln
|
||||
- name: 'Build obs-websocket 32-bit'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: msbuild /m /p:Configuration=RelWithDebInfo .\build32\obs-websocket.sln
|
||||
- name: 'Set PR artifact filename'
|
||||
shell: bash
|
||||
run: |
|
||||
FILENAME="obs-websocket-${{ env.GIT_HASH }}-Windows"
|
||||
echo "::set-env name=FILENAME::$FILENAME"
|
||||
- name: 'Package obs-websocket'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: |
|
||||
mkdir package
|
||||
cd package
|
||||
7z a "${{ env.WIN_FILENAME }}.zip" "..\release\*"
|
||||
iscc ..\installer\installer.iss /O. /F"${{ env.WIN_FILENAME }}-Installer"
|
||||
- name: 'Publish ${{ env.WIN_FILENAME }}.zip'
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v2-preview
|
||||
with:
|
||||
name: '${{ env.GIT_HASH }}-Windows'
|
||||
path: ${{ github.workspace }}/obs-websocket/package/*.zip
|
||||
- name: 'Publish ${{ env.WIN_FILENAME }}-Installer.exe'
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v2-preview
|
||||
with:
|
||||
name: '${{ env.GIT_HASH }}-Windows-Installer'
|
||||
path: ${{ github.workspace }}/obs-websocket/package/*.exe
|
||||
ubuntu64:
|
||||
name: "Linux/Ubuntu 64-bit"
|
||||
runs-on: [ubuntu-latest]
|
||||
if: contains(github.event.head_commit.message, '[skip ci]') != true
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ${{ github.workspace }}/obs-websocket
|
||||
submodules: 'recursive'
|
||||
- name: 'Checkout OBS'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: obsproject/obs-studio
|
||||
path: ${{ github.workspace }}/obs-studio
|
||||
submodules: 'recursive'
|
||||
- name: 'Get OBS-Studio git info'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
echo ::set-env name=OBS_GIT_BRANCH::$(git rev-parse --abbrev-ref HEAD)
|
||||
echo ::set-env name=OBS_GIT_HASH::$(git rev-parse --short HEAD)
|
||||
echo ::set-env name=OBS_GIT_TAG::$(git describe --tags --abbrev=0)
|
||||
- name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
git checkout ${{ env.OBS_GIT_TAG }}
|
||||
git submodule update
|
||||
- name: 'Get obs-websocket git info'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
echo ::set-env name=GIT_BRANCH::${{ github.event.pull_request.head.ref }}
|
||||
echo ::set-env name=GIT_HASH::$(git rev-parse --short HEAD)
|
||||
echo ::set-env name=GIT_TAG::$(git describe --tags --abbrev=0)
|
||||
- name: 'Install prerequisites (Apt)'
|
||||
shell: bash
|
||||
run: |
|
||||
sudo dpkg --add-architecture amd64
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get install -y \
|
||||
build-essential \
|
||||
checkinstall \
|
||||
cmake \
|
||||
libasound2-dev \
|
||||
libavcodec-dev \
|
||||
libavdevice-dev \
|
||||
libavfilter-dev \
|
||||
libavformat-dev \
|
||||
libavutil-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libfdk-aac-dev \
|
||||
libfontconfig-dev \
|
||||
libfreetype6-dev \
|
||||
libgl1-mesa-dev \
|
||||
libjack-jackd2-dev \
|
||||
libjansson-dev \
|
||||
libluajit-5.1-dev \
|
||||
libpulse-dev \
|
||||
libqt5x11extras5-dev \
|
||||
libspeexdsp-dev \
|
||||
libswresample-dev \
|
||||
libswscale-dev \
|
||||
libudev-dev \
|
||||
libv4l-dev \
|
||||
libva-dev \
|
||||
libvlc-dev \
|
||||
libx11-dev \
|
||||
libx264-dev \
|
||||
libxcb-randr0-dev \
|
||||
libxcb-shm0-dev \
|
||||
libxcb-xinerama0-dev \
|
||||
libxcomposite-dev \
|
||||
libxinerama-dev \
|
||||
libmbedtls-dev \
|
||||
pkg-config \
|
||||
python3-dev \
|
||||
qtbase5-dev \
|
||||
libqt5svg5-dev \
|
||||
swig
|
||||
- name: 'Configure OBS-Studio'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir ./build
|
||||
cd ./build
|
||||
cmake -DDISABLE_PLUGINS=YES -DENABLE_SCRIPTING=NO -DUNIX_STRUCTURE=YES -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
- name: 'Build OBS-Studio'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
cd ./build
|
||||
make -j4 libobs obs-frontend-api
|
||||
- name: 'Install OBS-Studio'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
shell: bash
|
||||
run: |
|
||||
cd ./build
|
||||
sudo cp ./libobs/libobs.so /usr/lib
|
||||
sudo cp ./UI/obs-frontend-api/libobs-frontend-api.so /usr/lib
|
||||
sudo mkdir -p /usr/include/obs
|
||||
sudo cp ../UI/obs-frontend-api/obs-frontend-api.h /usr/include/obs/obs-frontend-api.h
|
||||
- name: 'Configure obs-websocket'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir ./build
|
||||
cd ./build
|
||||
cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
- name: 'Build obs-websocket'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
cd ./build
|
||||
make -j4
|
||||
- name: 'Set PR artifact filename'
|
||||
shell: bash
|
||||
run: |
|
||||
FILENAME="obs-websocket-1-${{ env.GIT_HASH }}-1_amd64.deb"
|
||||
echo "::set-env name=FILENAME::$FILENAME"
|
||||
- name: 'Package ${{ env.FILENAME }}'
|
||||
if: success()
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION="1-${{ env.GIT_HASH }}-git"
|
||||
cd ./build
|
||||
sudo checkinstall -y --type=debian --fstrans=no -nodoc \
|
||||
--backup=no --deldoc=yes --install=no --pkgname=obs-websocket --pkgversion=$VERSION \
|
||||
--pkglicense="GPLv2.0" --maintainer="${{ github.event.pusher.email }}" --pkggroup="video" \
|
||||
--pkgsource="${{ github.event.repository.html_url }}" \
|
||||
--requires="obs-studio,libqt5core5a,libqt5widgets5,qt5-image-formats-plugins" \
|
||||
--pakdir="../package"
|
||||
sudo chmod ao+r ../package/*
|
||||
cd -
|
||||
- name: 'Publish ${{ env.FILENAME }}'
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v2-preview
|
||||
with:
|
||||
name: '${{ env.GIT_HASH }}-linux'
|
||||
path: '${{ github.workspace }}/obs-websocket/package/*.deb'
|
||||
macos64:
|
||||
name: "macOS 64-bit"
|
||||
runs-on: [macos-latest]
|
||||
if: contains(github.event.head_commit.message, '[skip ci]') != true
|
||||
env:
|
||||
MACOS_DEPS_VERSION: '2020-04-18'
|
||||
QT_VERSION: '5.14.1'
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ${{ github.workspace }}/obs-websocket
|
||||
submodules: 'recursive'
|
||||
- name: 'Checkout OBS'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: obsproject/obs-studio
|
||||
path: ${{ github.workspace }}/obs-studio
|
||||
submodules: 'recursive'
|
||||
- name: 'Get OBS-Studio git info'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
echo ::set-env name=OBS_GIT_BRANCH::$(git rev-parse --abbrev-ref HEAD)
|
||||
echo ::set-env name=OBS_GIT_HASH::$(git rev-parse --short HEAD)
|
||||
echo ::set-env name=OBS_GIT_TAG::$(git describe --tags --abbrev=0)
|
||||
- name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
git checkout ${{ env.OBS_GIT_TAG }}
|
||||
git submodule update
|
||||
- name: 'Get obs-websocket git info'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
echo ::set-env name=GIT_BRANCH::${{ github.event.pull_request.head.ref }}
|
||||
echo ::set-env name=GIT_HASH::$(git rev-parse --short HEAD)
|
||||
echo ::set-env name=GIT_TAG::$(git describe --tags --abbrev=0)
|
||||
- name: 'Install prerequisites (Homebrew)'
|
||||
shell: bash
|
||||
run: |
|
||||
brew bundle --file ${{ github.workspace }}/obs-websocket/CI/macos/Brewfile
|
||||
- name: 'Install prerequisite: Pre-built OBS dependencies'
|
||||
shell: bash
|
||||
run: |
|
||||
curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/osx-deps-${{ env.MACOS_DEPS_VERSION }}.tar.gz
|
||||
tar -xf ${{ github.workspace }}/osx-deps-${{ env.MACOS_DEPS_VERSION }}.tar.gz -C "/tmp"
|
||||
- name: 'Configure OBS Studio'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 -DENABLE_SCRIPTING=NO -DDepsPath=/tmp/obsdeps -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt/${{ env.QT_VERSION }}/lib/cmake ..
|
||||
- name: 'Build OBS Studio libraries'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
cd ./build
|
||||
make -j4 libobs obs-frontend-api
|
||||
- name: 'Configure obs-websocket'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DQTDIR=/usr/local/Cellar/qt/${{ env.QT_VERSION }} -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DLIBOBS_LIB=${{ github.workspace }}/obs-studio/libobs -DOBS_FRONTEND_LIB="${{ github.workspace }}/obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
- name: 'Build obs-websocket'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
cd ./build
|
||||
make -j4
|
||||
- name: 'Install prerequisite: Packages app'
|
||||
if: success()
|
||||
shell: bash
|
||||
run: |
|
||||
curl -L -O https://s3-us-west-2.amazonaws.com/obs-nightly/Packages.pkg
|
||||
sudo installer -pkg ${{ github.workspace }}/Packages.pkg -target /
|
||||
- name: 'Set PR artifact filename'
|
||||
shell: bash
|
||||
run: |
|
||||
FILENAME_UNSIGNED="obs-websocket-${{ env.GIT_HASH }}-macOS-Unsigned.pkg"
|
||||
echo "::set-env name=FILENAME_UNSIGNED::$FILENAME_UNSIGNED"
|
||||
- name: 'Fix linked dynamic library paths'
|
||||
if: success()
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
shell: bash
|
||||
run: |
|
||||
install_name_tool -change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets ./build/obs-websocket.so
|
||||
install_name_tool -change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui ./build/obs-websocket.so
|
||||
install_name_tool -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore ./build/obs-websocket.so
|
||||
echo "Dependencies for obs-websocket"
|
||||
otool -L ./build/obs-websocket.so
|
||||
- name: 'Package ${{ env.FILENAME }}'
|
||||
if: success()
|
||||
working-directory: ./obs-websocket
|
||||
shell: bash
|
||||
run: |
|
||||
packagesbuild ./CI/macos/obs-websocket.pkgproj
|
||||
mv ./release/obs-websocket.pkg ./release/${{ env.FILENAME_UNSIGNED }}
|
||||
- name: 'Publish ${{ env.FILENAME_UNSIGNED }} artifact'
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v2-preview
|
||||
with:
|
||||
name: '${{ env.GIT_HASH }}-macOS'
|
||||
path: ${{ github.workspace }}/obs-websocket/release/*.pkg
|
484
.github/workflows/tag_release.yml
vendored
Normal file
484
.github/workflows/tag_release.yml
vendored
Normal file
@ -0,0 +1,484 @@
|
||||
name: 'CI Multiplatform Release'
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
tags:
|
||||
- '[45].[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
name: 'Windows 32+64bit'
|
||||
runs-on: [windows-latest]
|
||||
env:
|
||||
QT_VERSION: '5.10.1'
|
||||
WINDOWS_DEPS_VERSION: '2017'
|
||||
CMAKE_GENERATOR: "Visual Studio 16 2019"
|
||||
CMAKE_SYSTEM_VERSION: "10.0"
|
||||
steps:
|
||||
- name: 'Add msbuild to PATH'
|
||||
uses: microsoft/setup-msbuild@v1.0.0
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ${{ github.workspace }}/obs-websocket
|
||||
submodules: 'recursive'
|
||||
- name: 'Checkout OBS'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: obsproject/obs-studio
|
||||
path: ${{ github.workspace }}/obs-studio
|
||||
submodules: 'recursive'
|
||||
- name: 'Get OBS-Studio git info'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
echo ::set-env name=OBS_GIT_BRANCH::$(git rev-parse --abbrev-ref HEAD)
|
||||
echo ::set-env name=OBS_GIT_HASH::$(git rev-parse --short HEAD)
|
||||
echo ::set-env name=OBS_GIT_TAG::$(git describe --tags --abbrev=0)
|
||||
- name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
git checkout ${{ env.OBS_GIT_TAG }}
|
||||
git submodule update
|
||||
- name: 'Get obs-websocket git info'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
echo ::set-env name=GIT_BRANCH::${{ github.event.pull_request.head.ref }}
|
||||
echo ::set-env name=GIT_HASH::$(git rev-parse --short HEAD)
|
||||
echo ::set-env name=GIT_TAG::$(git describe --tags --abbrev=0)
|
||||
- name: 'Install prerequisite: QT'
|
||||
run: |
|
||||
curl -kLO https://cdn-fastly.obsproject.com/downloads/Qt_${{ env.QT_VERSION }}.7z -f --retry 5 -C -
|
||||
7z x Qt_${{ env.QT_VERSION }}.7z -o"${{ github.workspace }}\cmbuild\QT"
|
||||
- name: 'Install prerequisite: Pre-built OBS dependencies'
|
||||
run: |
|
||||
curl -kLO https://cdn-fastly.obsproject.com/downloads/dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -f --retry 5 -C -
|
||||
7z x dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -o"${{ github.workspace }}\cmbuild\deps"
|
||||
- name: 'Restore OBS 32-bit build v${{ env.OBS_GIT_TAG }} from cache'
|
||||
id: build-cache-obs-32
|
||||
uses: actions/cache@v1
|
||||
env:
|
||||
CACHE_NAME: 'build-cache-obs-32'
|
||||
with:
|
||||
path: ${{ github.workspace }}/obs-studio/build32
|
||||
key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ env.CACHE_NAME }}-
|
||||
- name: 'Configure OBS 32-bit'
|
||||
if: steps.build-cache-obs-32.outputs.cache-hit != 'true'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
mkdir .\build32
|
||||
cd .\build32
|
||||
cmake -G "${{ env.CMAKE_GENERATOR }}" -A Win32 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2017" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win32" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES ..
|
||||
- name: 'Build OBS-Studio 32-bit'
|
||||
if: steps.build-cache-obs-32.outputs.cache-hit != 'true'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
msbuild /m /p:Configuration=RelWithDebInfo .\build32\libobs\libobs.vcxproj
|
||||
msbuild /m /p:Configuration=RelWithDebInfo .\build32\UI\obs-frontend-api\obs-frontend-api.vcxproj
|
||||
- name: 'Restore OBS 64-bit build v${{ env.OBS_GIT_TAG }} from cache'
|
||||
id: build-cache-obs-64
|
||||
uses: actions/cache@v1
|
||||
env:
|
||||
CACHE_NAME: 'build-cache-obs-64'
|
||||
with:
|
||||
path: ${{ github.workspace }}/obs-studio/build64
|
||||
key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ env.CACHE_NAME }}-
|
||||
- name: 'Configure OBS 64-bit'
|
||||
if: steps.build-cache-obs-64.outputs.cache-hit != 'true'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
mkdir .\build64
|
||||
cd .\build64
|
||||
cmake -G "${{ env.CMAKE_GENERATOR }}" -A x64 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2017_64" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win64" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES ..
|
||||
- name: 'Build OBS-Studio 64-bit'
|
||||
if: steps.build-cache-obs-64.outputs.cache-hit != 'true'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
msbuild /m /p:Configuration=RelWithDebInfo .\build64\libobs\libobs.vcxproj
|
||||
msbuild /m /p:Configuration=RelWithDebInfo .\build64\UI\obs-frontend-api\obs-frontend-api.vcxproj
|
||||
- name: 'Configure obs-websocket 64-bit'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: |
|
||||
mkdir .\build64
|
||||
cd .\build64
|
||||
cmake -G "${{ env.CMAKE_GENERATOR }}" -A x64 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2017_64" -DLibObs_DIR="${{ github.workspace }}\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="${{ github.workspace }}\obs-studio\libobs" -DLIBOBS_LIB="${{ github.workspace }}\obs-studio\build64\libobs\RelWithDebInfo\obs.lib" -DOBS_FRONTEND_LIB="${{ github.workspace }}\obs-studio\build64\UI\obs-frontend-api\RelWithDebInfo\obs-frontend-api.lib" ..
|
||||
- name: 'Configure obs-websocket 32-bit'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: |
|
||||
mkdir .\build32
|
||||
cd .\build32
|
||||
cmake -G "${{ env.CMAKE_GENERATOR }}" -A Win32 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2017" -DLibObs_DIR="${{ github.workspace }}\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="${{ github.workspace }}\obs-studio\libobs" -DLIBOBS_LIB="${{ github.workspace }}\obs-studio\build32\libobs\RelWithDebInfo\obs.lib" -DOBS_FRONTEND_LIB="${{ github.workspace }}\obs-studio\build32\UI\obs-frontend-api\RelWithDebInfo\obs-frontend-api.lib" ..
|
||||
- name: 'Build obs-websocket 64-bit'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: msbuild /m /p:Configuration=RelWithDebInfo .\build64\obs-websocket.sln
|
||||
- name: 'Build obs-websocket 32-bit'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: msbuild /m /p:Configuration=RelWithDebInfo .\build32\obs-websocket.sln
|
||||
- name: 'Set release filename'
|
||||
shell: bash
|
||||
run: |
|
||||
FILENAME="obs-websocket-${{ env.GIT_TAG }}-Windows"
|
||||
echo "::set-env name=WIN_FILENAME::$FILENAME"
|
||||
- name: 'Package obs-websocket'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: |
|
||||
mkdir package
|
||||
cd package
|
||||
7z a "${{ env.WIN_FILENAME }}.zip" "..\release\*"
|
||||
iscc ..\installer\installer.iss /O. /F"${{ env.WIN_FILENAME }}-Installer"
|
||||
- name: 'Publish ${{ env.WIN_FILENAME }}.zip'
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v2-preview
|
||||
with:
|
||||
name: '${{ env.GIT_TAG }}-Windows'
|
||||
path: ${{ github.workspace }}/obs-websocket/package/*.zip
|
||||
- name: 'Publish ${{ env.WIN_FILENAME }}-Installer.exe'
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v2-preview
|
||||
with:
|
||||
name: '${{ env.GIT_TAG }}-Windows-Installer'
|
||||
path: ${{ github.workspace }}/obs-websocket/package/*.exe
|
||||
ubuntu64:
|
||||
name: "Linux/Ubuntu 64-bit"
|
||||
runs-on: [ubuntu-latest]
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ${{ github.workspace }}/obs-websocket
|
||||
submodules: 'recursive'
|
||||
- name: 'Checkout OBS'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: obsproject/obs-studio
|
||||
path: ${{ github.workspace }}/obs-studio
|
||||
submodules: 'recursive'
|
||||
- name: 'Get OBS-Studio git info'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
echo ::set-env name=OBS_GIT_BRANCH::$(git rev-parse --abbrev-ref HEAD)
|
||||
echo ::set-env name=OBS_GIT_HASH::$(git rev-parse --short HEAD)
|
||||
echo ::set-env name=OBS_GIT_TAG::$(git describe --tags --abbrev=0)
|
||||
- name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
git checkout ${{ env.OBS_GIT_TAG }}
|
||||
git submodule update
|
||||
- name: 'Get obs-websocket git info'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
echo ::set-env name=GIT_BRANCH::${{ github.event.pull_request.head.ref }}
|
||||
echo ::set-env name=GIT_HASH::$(git rev-parse --short HEAD)
|
||||
echo ::set-env name=GIT_TAG::$(git describe --tags --abbrev=0)
|
||||
- name: 'Install prerequisites (Apt)'
|
||||
shell: bash
|
||||
run: |
|
||||
sudo dpkg --add-architecture amd64
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get install -y \
|
||||
build-essential \
|
||||
checkinstall \
|
||||
cmake \
|
||||
libasound2-dev \
|
||||
libavcodec-dev \
|
||||
libavdevice-dev \
|
||||
libavfilter-dev \
|
||||
libavformat-dev \
|
||||
libavutil-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libfdk-aac-dev \
|
||||
libfontconfig-dev \
|
||||
libfreetype6-dev \
|
||||
libgl1-mesa-dev \
|
||||
libjack-jackd2-dev \
|
||||
libjansson-dev \
|
||||
libluajit-5.1-dev \
|
||||
libpulse-dev \
|
||||
libqt5x11extras5-dev \
|
||||
libspeexdsp-dev \
|
||||
libswresample-dev \
|
||||
libswscale-dev \
|
||||
libudev-dev \
|
||||
libv4l-dev \
|
||||
libva-dev \
|
||||
libvlc-dev \
|
||||
libx11-dev \
|
||||
libx264-dev \
|
||||
libxcb-randr0-dev \
|
||||
libxcb-shm0-dev \
|
||||
libxcb-xinerama0-dev \
|
||||
libxcomposite-dev \
|
||||
libxinerama-dev \
|
||||
libmbedtls-dev \
|
||||
pkg-config \
|
||||
python3-dev \
|
||||
qtbase5-dev \
|
||||
libqt5svg5-dev \
|
||||
swig
|
||||
- name: 'Configure OBS-Studio'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir ./build
|
||||
cd ./build
|
||||
cmake -DDISABLE_PLUGINS=YES -DENABLE_SCRIPTING=NO -DUNIX_STRUCTURE=YES -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
- name: 'Build OBS-Studio'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
cd ./build
|
||||
make -j4 libobs obs-frontend-api
|
||||
- name: 'Install OBS-Studio'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
shell: bash
|
||||
run: |
|
||||
cd ./build
|
||||
sudo cp ./libobs/libobs.so /usr/lib
|
||||
sudo cp ./UI/obs-frontend-api/libobs-frontend-api.so /usr/lib
|
||||
sudo mkdir -p /usr/include/obs
|
||||
sudo cp ../UI/obs-frontend-api/obs-frontend-api.h /usr/include/obs/obs-frontend-api.h
|
||||
- name: 'Configure obs-websocket'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir ./build
|
||||
cd ./build
|
||||
cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
- name: 'Build obs-websocket'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
cd ./build
|
||||
make -j4
|
||||
- name: 'Set release filename'
|
||||
shell: bash
|
||||
run: |
|
||||
FILENAME="obs-websocket-${{ env.GIT_TAG }}-1_amd64.deb"
|
||||
echo "::set-env name=LINUX_FILENAME::$FILENAME"
|
||||
- name: 'Package ${{ env.LINUX_FILENAME }}'
|
||||
if: success()
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION="${{ env.GIT_TAG }}"
|
||||
cd ./build
|
||||
sudo checkinstall -y --type=debian --fstrans=no -nodoc \
|
||||
--backup=no --deldoc=yes --install=no --pkgname=obs-websocket --pkgversion=$VERSION \
|
||||
--pkglicense="GPLv2.0" --maintainer="${{ github.event.pusher.email }}" --pkggroup="video" \
|
||||
--pkgsource="${{ github.event.repository.html_url }}" \
|
||||
--requires="obs-studio,libqt5core5a,libqt5widgets5,qt5-image-formats-plugins" \
|
||||
--pakdir="../package"
|
||||
sudo chmod ao+r ../package/*
|
||||
cd -
|
||||
- name: 'Publish ${{ env.LINUX_FILENAME }}'
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v2-preview
|
||||
with:
|
||||
name: '${{ env.GIT_TAG }}-linux'
|
||||
path: '${{ github.workspace }}/obs-websocket/package/*.deb'
|
||||
macos64:
|
||||
name: "macOS 64-bit"
|
||||
runs-on: [macos-latest]
|
||||
env:
|
||||
MACOS_DEPS_VERSION: '2020-04-18'
|
||||
QT_VERSION: '5.14.1'
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ${{ github.workspace }}/obs-websocket
|
||||
submodules: 'recursive'
|
||||
- name: 'Checkout OBS'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: obsproject/obs-studio
|
||||
path: ${{ github.workspace }}/obs-studio
|
||||
submodules: 'recursive'
|
||||
- name: 'Get OBS-Studio git info'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
echo ::set-env name=OBS_GIT_BRANCH::$(git rev-parse --abbrev-ref HEAD)
|
||||
echo ::set-env name=OBS_GIT_HASH::$(git rev-parse --short HEAD)
|
||||
echo ::set-env name=OBS_GIT_TAG::$(git describe --tags --abbrev=0)
|
||||
- name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
git checkout ${{ env.OBS_GIT_TAG }}
|
||||
git submodule update
|
||||
- name: 'Get obs-websocket git info'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
echo ::set-env name=GIT_BRANCH::${{ github.event.pull_request.head.ref }}
|
||||
echo ::set-env name=GIT_HASH::$(git rev-parse --short HEAD)
|
||||
echo ::set-env name=GIT_TAG::$(git describe --tags --abbrev=0)
|
||||
- name: 'Install prerequisites (Homebrew)'
|
||||
shell: bash
|
||||
run: |
|
||||
brew bundle --file ${{ github.workspace }}/obs-websocket/CI/macos/Brewfile
|
||||
- name: 'Install prerequisite: Pre-built OBS dependencies'
|
||||
shell: bash
|
||||
run: |
|
||||
curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/osx-deps-${{ env.MACOS_DEPS_VERSION }}.tar.gz
|
||||
tar -xf ${{ github.workspace }}/osx-deps-${{ env.MACOS_DEPS_VERSION }}.tar.gz -C "/tmp"
|
||||
- name: 'Configure OBS Studio'
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 -DENABLE_SCRIPTING=NO -DDepsPath=/tmp/obsdeps -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt/${{ env.QT_VERSION }}/lib/cmake ..
|
||||
- name: 'Build OBS Studio libraries'
|
||||
working-directory: ${{ github.workspace }}/obs-studio
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
cd ./build
|
||||
make -j4 libobs obs-frontend-api
|
||||
- name: 'Configure obs-websocket'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DQTDIR=/usr/local/Cellar/qt/${{ env.QT_VERSION }} -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DLIBOBS_LIB=${{ github.workspace }}/obs-studio/libobs -DOBS_FRONTEND_LIB="${{ github.workspace }}/obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
- name: 'Build obs-websocket'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
cd ./build
|
||||
make -j4
|
||||
- name: 'Install prerequisite: Packages app'
|
||||
if: success()
|
||||
shell: bash
|
||||
run: |
|
||||
curl -L -O https://s3-us-west-2.amazonaws.com/obs-nightly/Packages.pkg
|
||||
sudo installer -pkg ${{ github.workspace }}/Packages.pkg -target /
|
||||
- name: 'Set release filename'
|
||||
if: success() && startsWith(github.ref, 'refs/tags')
|
||||
shell: bash
|
||||
run: |
|
||||
FILENAME_UNSIGNED="obs-websocket-${{ env.GIT_TAG }}-macOS-Unsigned.pkg"
|
||||
FILENAME="obs-websocket-${{ env.GIT_TAG }}-macOS.pkg"
|
||||
echo "::set-env name=MAC_FILENAME_UNSIGNED::$FILENAME_UNSIGNED"
|
||||
echo "::set-env name=MAC_FILENAME::$FILENAME"
|
||||
- name: 'Fix linked dynamic library paths'
|
||||
if: success()
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
shell: bash
|
||||
run: |
|
||||
install_name_tool -change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets ./build/obs-websocket.so
|
||||
install_name_tool -change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui ./build/obs-websocket.so
|
||||
install_name_tool -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore ./build/obs-websocket.so
|
||||
echo "Dependencies for obs-websocket"
|
||||
otool -L ./build/obs-websocket.so
|
||||
- name: 'Install Apple Developer Certificate'
|
||||
if: success()
|
||||
uses: apple-actions/import-codesign-certs@253ddeeac23f2bdad1646faac5c8c2832e800071
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.MACOS_CERT_CODESIGN }}
|
||||
p12-password: ${{ secrets.MACOS_CERT_PASS }}
|
||||
- name: 'Code signing'
|
||||
if: success()
|
||||
working-directory: ./obs-websocket
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
codesign --sign "${{ secrets.MACOS_IDENT_CODESIGN }}" ./build/obs-websocket.so
|
||||
packagesbuild ./CI/macos/obs-websocket.pkgproj
|
||||
mv ./release/obs-websocket.pkg ./release/${{ env.MAC_FILENAME_UNSIGNED }}
|
||||
productsign --sign "${{ secrets.MACOS_IDENT_INSTALLER }}" ./release/${{ env.MAC_FILENAME_UNSIGNED }} ./release/${{ env.MAC_FILENAME }}
|
||||
rm ./release/${{ env.MAC_FILENAME_UNSIGNED }}
|
||||
- name: 'Notarization'
|
||||
if: success()
|
||||
working-directory: ./obs-websocket
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
xcrun altool --store-password-in-keychain-item "AC_PASSWORD" -u "${{ secrets.MACOS_IDENT_USER }}" -p "${{ secrets.MACOS_IDENT_PASS }}"
|
||||
xcnotary precheck ./release/${{ env.MAC_FILENAME }}
|
||||
if [ "$?" -eq 0 ]; then xcnotary notarize ./release/${{ env.MAC_FILENAME }} --developer-account "${{ secrets.MACOS_IDENT_USER }}" --developer-password-keychain-item "AC_PASSWORD" --provider "${{ secrets.MACOS_IDENT_PROVIDER }}"; fi
|
||||
- name: 'Publish ${{ env.MAC_FILENAME }} artifact'
|
||||
if: success() && startsWith(github.ref, 'refs/tags')
|
||||
uses: actions/upload-artifact@v2-preview
|
||||
with:
|
||||
name: '${{ env.GIT_TAG }}-macOS'
|
||||
path: ${{ github.workspace }}/obs-websocket/release/*.pkg
|
||||
make-release:
|
||||
name: 'Create and upload release'
|
||||
runs-on: [ubuntu-latest]
|
||||
needs: [windows, ubuntu64, macos64]
|
||||
steps:
|
||||
- name: 'Get the version'
|
||||
shell: bash
|
||||
id: get_version
|
||||
run: |
|
||||
echo ::set-env name=TAG_VERSION::${GITHUB_REF/refs\/tags\//}
|
||||
- name: 'Create Release'
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ env.TAG_VERSION }}
|
||||
release_name: obs-websocket ${{ env.TAG_VERSION }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
- name: 'Download release artifacts'
|
||||
uses: actions/download-artifact@v2-preview
|
||||
- name: 'Upload Windows .zip artifact to release'
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ${{ github.workspace }}/${{ env.TAG_VERSION }}-Windows/obs-websocket-${{ env.TAG_VERSION }}-Windows.zip
|
||||
asset_name: obs-websocket-${{ env.TAG_VERSION }}-Windows.zip
|
||||
asset_content_type: application/zip
|
||||
- name: 'Upload Windows .exe artifact to release'
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ${{ github.workspace }}/${{ env.TAG_VERSION }}-Windows-Installer/obs-websocket-${{ env.TAG_VERSION }}-Windows-Installer.exe
|
||||
asset_name: obs-websocket-${{ env.TAG_VERSION }}-Windows-Installer.exe
|
||||
asset_content_type: application/zip
|
||||
- name: 'Upload Linux artifact to release'
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ${{ github.workspace }}/${{ env.TAG_VERSION }}-linux/obs-websocket_${{ env.TAG_VERSION }}-1_amd64.deb
|
||||
asset_name: obs-websocket-${{ env.TAG_VERSION }}-1_amd64.deb
|
||||
asset_content_type: application/octet-stream
|
||||
- name: 'Upload macOS artifact to release'
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ${{ github.workspace }}/${{ env.TAG_VERSION }}-macOS/obs-websocket-${{ env.TAG_VERSION }}-macOS.pkg
|
||||
asset_name: obs-websocket-${{ env.TAG_VERSION }}-macOS.pkg
|
||||
asset_content_type: application/octet-stream
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -4,6 +4,7 @@
|
||||
/build32/
|
||||
/build64/
|
||||
/release/
|
||||
/package/
|
||||
/installer/Output/
|
||||
|
||||
.vscode
|
||||
.idea
|
||||
.vscode
|
||||
|
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -1,3 +1,6 @@
|
||||
[submodule "deps/mbedtls"]
|
||||
path = deps/mbedtls
|
||||
url = https://github.com/ARMmbed/mbedtls
|
||||
[submodule "deps/websocketpp"]
|
||||
path = deps/websocketpp
|
||||
url = https://github.com/zaphoyd/websocketpp.git
|
||||
[submodule "deps/asio"]
|
||||
path = deps/asio
|
||||
url = https://github.com/chriskohlhoff/asio.git
|
||||
|
69
.travis.yml
69
.travis.yml
@ -1,69 +0,0 @@
|
||||
language: cpp
|
||||
|
||||
env:
|
||||
global:
|
||||
# AWS key ID
|
||||
- secure: pAiNUGVbjP12BfnWPk0FFTkbnk4Tocvv88XiT3rzRqkQaD7/iyEogLBfHM4nOEgFiIMHbC41aE83w5JgRNPwn6mTgoQBOglzqq1tGuXfqPyV2VStk8beji1evubGoVjjPaoPTFyIdQc5GGxdHyogI/ed9Hb3ccyykYvjyolj9XoCiW42QHx60AHGwl+So+dEa8xydj9SLRPlZ/AitmI/cPVN3YotA7s37BLFiab54enxk7T4rwpR1nU0HVfoCpn5F4wZYxRq+LlSVFzC8vVE9cpDSLS5kjrZIZaT18tYG1/untCj+wqMIZbghaJXLtPSRW2YPHcJTz8q1YSXnJ19+0uiAIMAqaVv0kD5BAM97byYDBW+b9H6SYFkb/Pw/qcK9amMzMBjDPFpYFkl9Q2kzhsNs3HsZf/flSZjtrkQJiP3SOi/KvKzVK9X4Wym6hYZWHgmMTTYFrvr6BYnf2GkpfKNjm1d2kc0NNrq4d5H4NOEQB8MP+QH+o+BPeM6d9dthrUc1Pw+BXzOAr85CN4qtpPGoAl/Dbfgd6eu/88E2LpUufW2VFAOPWjykSOqzSN3orh7AaWuE34VFEnQ+2y3uIE8AKoyXzJv6zYkyNnNewKZeGe2kKYNwLn5UxQA9JEj7a+tvVevk4xBSkkjFAvjSG2z8/F1FXNbEfoLX1Hz/bU=
|
||||
# AWS key secret
|
||||
- secure: bGwljoP3E1OVBXLXox0O6p8kwQXLcNQ8YDKVa4H8u9Y+Ic7uqE4iV3rYS3ynNWSBMVRWY3ZbyClnhrCNwRhBAlcd8qWSJdpjVzs6HdQyzhuKa1P3V4FJPb7upGP/5R/DECGwex8Mun9dmXpYDak75LxfKIJUidPis5VDCYqul7k/xVVCou6Ctjpj7vQhWXDj2G/py+mdB8DERhymnQCtyK1Ziu8c4QlFKByZmnD72GFm/h3JPI1Pq1V2mz3x6x6GaYjb9Rdbd0UNwqjGQX4q2M/c3GEJa6B2JBCoTncawNZBNnPUF9qtv+zh0TNaNHMRWX13AJ/qYB+nVDub0C9b/6Mc48mt0Tv4ze15MproVrylZdV6qHYEG8yGPBqpTVbRP6gv6Y2TXIHWoTzqA+F/Gv2IDChyHXsld/MQQS2MSo5iaYktIrZKtX8Z0qAmTzPwIVBromaSI3vrE7UH0fRSQ6fAM8+Tn+MRthOBdqu23kS1dnG+X2CPbUhBfsJp0OSwVQD5jQtA51/sREVeGFiJvzQIkvwQDjb5MYilsRnwmoBXemkLmqaviXVY4rz1o5AIvz2pgZS2YggK1xHZCuI5tSjcNEkb77VwZTfsqrdDo9EJh6VgfdnGlHQhR2/A5hUJ4ANpJ/LgZlgfVp71Xg2GWQW6M4Znc5uj6A6xLBkO6FA=
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
env: _generate_docs
|
||||
script: "./CI/generate-docs.sh"
|
||||
|
||||
- os: linux
|
||||
env: _linux_build
|
||||
dist: trusty
|
||||
sudo: required
|
||||
services:
|
||||
- docker
|
||||
before_install:
|
||||
- docker run -d --name xenial -v $(dirname $(pwd)):/root -v /home/travis/package:/package
|
||||
-e TRAVIS_BRANCH="$TRAVIS_BRANCH" -e TRAVIS_TAG="$TRAVIS_TAG" -w /root nimmis/ubuntu:16.04
|
||||
- docker exec -it xenial /root/obs-websocket/CI/install-dependencies-xenial.sh
|
||||
script:
|
||||
- docker exec -it xenial /root/obs-websocket/CI/build-xenial.sh
|
||||
after_success:
|
||||
- docker exec -it xenial /root/obs-websocket/CI/package-xenial.sh
|
||||
|
||||
- os: osx
|
||||
env: _macos_build
|
||||
osx_image: xcode8.3
|
||||
before_install: "./CI/install-dependencies-osx.sh"
|
||||
script: "./CI/build-osx.sh"
|
||||
after_success:
|
||||
- ./CI/package-osx.sh
|
||||
|
||||
deploy:
|
||||
- provider: s3
|
||||
region: eu-central-1
|
||||
bucket: obs-websocket-linux-builds
|
||||
access_key_id: "$AWS_ID"
|
||||
secret_access_key: "$AWS_SECRET"
|
||||
local_dir: /home/travis/package
|
||||
skip_cleanup: true
|
||||
acl: public_read
|
||||
on:
|
||||
repo: Palakis/obs-websocket
|
||||
condition:
|
||||
- "$TRAVIS_OS_NAME = linux"
|
||||
- "-d /home/travis/package"
|
||||
all_branches: true
|
||||
- provider: s3
|
||||
region: eu-central-1
|
||||
bucket: obs-websocket-osx-builds
|
||||
access_key_id: "$AWS_ID"
|
||||
secret_access_key: "$AWS_SECRET"
|
||||
local_dir: release
|
||||
skip_cleanup: true
|
||||
acl: public_read
|
||||
on:
|
||||
repo: Palakis/obs-websocket
|
||||
condition: "$TRAVIS_OS_NAME = osx"
|
||||
all_branches: true
|
53
BUILDING.md
53
BUILDING.md
@ -1,29 +1,66 @@
|
||||
# Compiling obs-websocket
|
||||
|
||||
## Prerequisites
|
||||
You'll need [QT 5.7.0](https://download.qt.io/official_releases/qt/5.7/5.7.0/), CMake, and a working development environment for OBS Studio installed on your computer.
|
||||
|
||||
You'll need [Qt 5.10.x](https://download.qt.io/official_releases/qt/5.10/),
|
||||
[CMake](https://cmake.org/download/) and a working [OBS Studio development environment](https://obsproject.com/wiki/install-instructions) installed on your
|
||||
computer.
|
||||
|
||||
## Windows
|
||||
|
||||
In cmake-gui, you'll have to set the following variables :
|
||||
|
||||
- **QTDIR** (path) : location of the Qt environment suited for your compiler and architecture
|
||||
- **LIBOBS_INCLUDE_DIR** (path) : location of the libobs subfolder in the source code of OBS Studio
|
||||
- **LIBOBS_LIB** (filepath) : location of the obs.lib file
|
||||
- **OBS_FRONTEND_LIB** (filepath) : location of the obs-frontend-api.lib file
|
||||
|
||||
## Linux
|
||||
On Debian/Ubuntu :
|
||||
```
|
||||
sudo apt-get install libqt5websockets5-dev
|
||||
|
||||
On Debian/Ubuntu :
|
||||
|
||||
```shell
|
||||
sudo apt-get install libboost-all-dev
|
||||
git clone --recursive https://github.com/Palakis/obs-websocket.git
|
||||
cd obs-websocket
|
||||
mkdir build && cd build
|
||||
cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=true ..
|
||||
make -j4
|
||||
sudo make install
|
||||
```
|
||||
|
||||
On other linux OS's, use this cmake command instead:
|
||||
|
||||
```shell
|
||||
cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
```
|
||||
|
||||
## OS X
|
||||
*To do*
|
||||
|
||||
As a prerequisite, you will need Xcode for your current OSX version, the Xcode command line tools, and [Homebrew](https://brew.sh/).
|
||||
Homebrew's setup will guide you in getting your system set up, you should be good to go once Homebrew is successfully up and running.
|
||||
|
||||
Use of the macOS CI scripts is recommended. Please note that these
|
||||
scripts install new software and can change several settings on your system. An
|
||||
existing obs-studio development environment is not required, as
|
||||
`install-build-obs-macos.sh` will install it for you. If you already have a
|
||||
working obs-studio development environment and have built obs-studio, you can
|
||||
skip that script.
|
||||
|
||||
Of course, you're encouraged to dig through the contents of these scripts to
|
||||
look for issues or specificities.
|
||||
|
||||
```shell
|
||||
git clone --recursive https://github.com/Palakis/obs-websocket.git
|
||||
cd obs-websocket
|
||||
./CI/install-dependencies-macos.sh
|
||||
./CI/install-build-obs-macos.sh
|
||||
./CI/build-macos.sh
|
||||
./CI/package-macos.sh
|
||||
```
|
||||
|
||||
This will result in a ready-to-use `obs-websocket.pkg` installer in the `release` subfolder.
|
||||
|
||||
## Automated Builds
|
||||
- Windows : [](https://ci.appveyor.com/project/Palakis/obs-websocket/history)
|
||||
- Linux & OS X : [](https://travis-ci.org/Palakis/obs-websocket)
|
||||
|
||||
[](https://dev.azure.com/Palakis/obs-websocket/_build/latest?definitionId=2&branchName=4.x-current)
|
||||
|
28
CI/build-macos.sh
Executable file
28
CI/build-macos.sh
Executable file
@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
|
||||
OSTYPE=$(uname)
|
||||
|
||||
if [ "${OSTYPE}" != "Darwin" ]; then
|
||||
echo "[obs-websocket - Error] macOS build script can be run on Darwin-type OS only."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HAS_CMAKE=$(type cmake 2>/dev/null)
|
||||
|
||||
if [ "${HAS_CMAKE}" = "" ]; then
|
||||
echo "[obs-websocket - Error] CMake not installed - please run 'install-dependencies-macos.sh' first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#export QT_PREFIX="$(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)"
|
||||
|
||||
echo "[obs-websocket] Building 'obs-websocket' for macOS."
|
||||
mkdir -p build && cd build
|
||||
cmake .. \
|
||||
-DQTDIR=/usr/local/opt/qt \
|
||||
-DLIBOBS_INCLUDE_DIR=../../obs-studio/libobs \
|
||||
-DLIBOBS_LIB=../../obs-studio/libobs \
|
||||
-DOBS_FRONTEND_LIB="$(pwd)/../../obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
&& make -j4
|
@ -1,12 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
#export QT_PREFIX="$(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)"
|
||||
|
||||
mkdir build && cd build
|
||||
cmake .. \
|
||||
-DQTDIR=/usr/local/opt/qt \
|
||||
-DLIBOBS_INCLUDE_DIR=../../obs-studio/libobs \
|
||||
-DOBS_FRONTEND_LIB="$(pwd)/../../obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
&& make -j4
|
6
CI/build-ubuntu.sh
Executable file
6
CI/build-ubuntu.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
mkdir build && cd build
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=true ..
|
||||
make -j4
|
@ -1,8 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
cd /root/obs-websocket
|
||||
|
||||
mkdir build && cd build
|
||||
cmake -DLIBOBS_INCLUDE_DIR="../../obs-studio/libobs" -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
make -j4
|
6
CI/download-obs-deps.cmd
Normal file
6
CI/download-obs-deps.cmd
Normal file
@ -0,0 +1,6 @@
|
||||
if not exist %DepsBasePath% (
|
||||
curl -o %DepsBasePath%.zip -kLO https://obsproject.com/downloads/dependencies2017.zip -f --retry 5 -C -
|
||||
7z x %DepsBasePath%.zip -o%DepsBasePath%
|
||||
) else (
|
||||
echo "OBS dependencies are already there. Download skipped."
|
||||
)
|
@ -1,8 +1,12 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
echo "-- Generating documentation."
|
||||
echo "-- Node version: $(node -v)"
|
||||
echo "-- NPM version: $(npm -v)"
|
||||
|
||||
git fetch origin
|
||||
git checkout ${CHECKOUT_REF/refs\/heads\//}
|
||||
|
||||
cd docs
|
||||
npm install
|
||||
npm run build
|
||||
@ -10,23 +14,18 @@ npm run build
|
||||
echo "-- Documentation successfully generated."
|
||||
|
||||
if git diff --quiet; then
|
||||
echo "-- No documentation changes to commit."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "master" ]; then
|
||||
echo "-- Skipping documentation deployment because this is either a pull request or a non-master branch."
|
||||
exit 0
|
||||
echo "-- No documentation changes to commit."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
REMOTE_URL="$(git config remote.origin.url)"
|
||||
TARGET_REPO=${REMOTE_URL/https:\/\/github.com\//github.com/}
|
||||
GITHUB_REPO=https://${GH_TOKEN:-git}@${TARGET_REPO}
|
||||
|
||||
git config user.name "Travis CI"
|
||||
git config user.name "Azure CI"
|
||||
git config user.email "$COMMIT_AUTHOR_EMAIL"
|
||||
|
||||
git add ./generated
|
||||
git pull
|
||||
git commit -m "docs(travis): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
|
||||
git push -q $GITHUB_REPO HEAD:$TRAVIS_BRANCH
|
||||
git commit -m "docs(ci): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
|
||||
git push -q $GITHUB_REPO
|
||||
|
42
CI/install-build-obs-macos.sh
Executable file
42
CI/install-build-obs-macos.sh
Executable file
@ -0,0 +1,42 @@
|
||||
#!/bin/sh
|
||||
|
||||
OSTYPE=$(uname)
|
||||
|
||||
if [ "${OSTYPE}" != "Darwin" ]; then
|
||||
echo "[obs-websocket - Error] macOS obs-studio build script can be run on Darwin-type OS only."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HAS_CMAKE=$(type cmake 2>/dev/null)
|
||||
HAS_GIT=$(type git 2>/dev/null)
|
||||
|
||||
if [ "${HAS_CMAKE}" = "" ]; then
|
||||
echo "[obs-websocket - Error] CMake not installed - please run 'install-dependencies-macos.sh' first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${HAS_GIT}" = "" ]; then
|
||||
echo "[obs-websocket - Error] Git not installed - please install Xcode developer tools or via Homebrew."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[obs-websocket] Downloading and unpacking OBS dependencies"
|
||||
wget --quiet --retry-connrefused --waitretry=1 https://obs-nightly.s3.amazonaws.com/osx-deps-2018-08-09.tar.gz
|
||||
tar -xf ./osx-deps-2018-08-09.tar.gz -C /tmp
|
||||
|
||||
# Build obs-studio
|
||||
cd ..
|
||||
echo "[obs-websocket] Cloning obs-studio from GitHub.."
|
||||
git clone https://github.com/obsproject/obs-studio
|
||||
cd obs-studio
|
||||
OBSLatestTag=$(git describe --tags --abbrev=0)
|
||||
git checkout $OBSLatestTag
|
||||
mkdir build && cd build
|
||||
echo "[obs-websocket] Building obs-studio.."
|
||||
cmake .. \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 \
|
||||
-DDISABLE_PLUGINS=true \
|
||||
-DENABLE_SCRIPTING=0 \
|
||||
-DDepsPath=/tmp/obsdeps \
|
||||
-DCMAKE_PREFIX_PATH=/usr/local/opt/qt/lib/cmake \
|
||||
&& make -j4
|
56
CI/install-dependencies-macos.sh
Executable file
56
CI/install-dependencies-macos.sh
Executable file
@ -0,0 +1,56 @@
|
||||
#!/bin/sh
|
||||
|
||||
|
||||
|
||||
OSTYPE=$(uname)
|
||||
|
||||
if [ "${OSTYPE}" != "Darwin" ]; then
|
||||
echo "[obs-websocket - Error] macOS install dependencies script can be run on Darwin-type OS only."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HAS_BREW=$(type brew 2>/dev/null)
|
||||
|
||||
if [ "${HAS_BREW}" = "" ]; then
|
||||
echo "[obs-websocket - Error] Please install Homebrew (https://www.brew.sh/) to build obs-websocket on macOS."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# OBS Studio deps
|
||||
echo "[obs-websocket] Updating Homebrew.."
|
||||
brew update >/dev/null
|
||||
echo "[obs-websocket] Checking installed Homebrew formulas.."
|
||||
BREW_PACKAGES=$(brew list)
|
||||
BREW_DEPENDENCIES="jack speexdsp ccache swig mbedtls"
|
||||
|
||||
for DEPENDENCY in ${BREW_DEPENDENCIES}; do
|
||||
if echo "${BREW_PACKAGES}" | grep -q "^${DEPENDENCY}\$"; then
|
||||
echo "[obs-websocket] Upgrading OBS-Studio dependency '${DEPENDENCY}'.."
|
||||
brew upgrade ${DEPENDENCY} 2>/dev/null
|
||||
else
|
||||
echo "[obs-websocket] Installing OBS-Studio dependency '${DEPENDENCY}'.."
|
||||
brew install ${DEPENDENCY} 2>/dev/null
|
||||
fi
|
||||
done
|
||||
|
||||
# qtwebsockets deps
|
||||
echo "[obs-websocket] Installing obs-websocket dependency 'QT 5.10.1'.."
|
||||
|
||||
brew install ./CI/macos/qt.rb
|
||||
|
||||
# Pin this version of QT5 to avoid `brew upgrade`
|
||||
# upgrading it to incompatible version
|
||||
brew pin qt
|
||||
|
||||
# Fetch and install Packages app
|
||||
# =!= NOTICE =!=
|
||||
# Installs a LaunchDaemon under /Library/LaunchDaemons/fr.whitebox.packages.build.dispatcher.plist
|
||||
# =!= NOTICE =!=
|
||||
|
||||
HAS_PACKAGES=$(type packagesbuild 2>/dev/null)
|
||||
|
||||
if [ "${HAS_PACKAGES}" = "" ]; then
|
||||
echo "[obs-websocket] Installing Packaging app (might require password due to 'sudo').."
|
||||
curl -o './Packages.pkg' --retry-connrefused -s --retry-delay 1 'https://s3-us-west-2.amazonaws.com/obs-nightly/Packages.pkg'
|
||||
sudo installer -pkg ./Packages.pkg -target /
|
||||
fi
|
@ -1,28 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
# OBS Studio deps
|
||||
brew update
|
||||
brew install ffmpeg
|
||||
brew install libav
|
||||
|
||||
# qtwebsockets deps
|
||||
brew install qt5
|
||||
#echo "Qt path: $(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)"
|
||||
|
||||
# Build obs-studio
|
||||
cd ..
|
||||
git clone --recursive https://github.com/jp9000/obs-studio
|
||||
cd obs-studio
|
||||
git checkout 19.0.3
|
||||
mkdir build && cd build
|
||||
cmake .. \
|
||||
-DCMAKE_PREFIX_PATH=/usr/local/opt/qt/lib/cmake \
|
||||
&& make -j4
|
||||
|
||||
sudo make install
|
||||
|
||||
# Packages app
|
||||
cd ..
|
||||
curl -L -O https://www.slepin.fr/obs-websocket/ci/Packages.pkg -f --retry 5 -C -
|
||||
sudo installer -pkg ./Packages.pkg -target /
|
19
CI/install-dependencies-ubuntu.sh
Executable file
19
CI/install-dependencies-ubuntu.sh
Executable file
@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
sudo add-apt-repository -y ppa:obsproject/obs-studio
|
||||
sudo apt-get -qq update
|
||||
|
||||
sudo apt-get install -y \
|
||||
libc-dev-bin \
|
||||
libc6-dev git \
|
||||
build-essential \
|
||||
checkinstall \
|
||||
cmake \
|
||||
obs-studio \
|
||||
qtbase5-dev
|
||||
|
||||
# Dirty hack
|
||||
sudo wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/26.0.0/UI/obs-frontend-api/obs-frontend-api.h
|
||||
|
||||
sudo ldconfig
|
@ -1,57 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
# OBS Studio deps
|
||||
apt-get -qq update
|
||||
apt-get install -y \
|
||||
libc-dev-bin libc6-dev \
|
||||
git \
|
||||
build-essential
|
||||
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
checkinstall \
|
||||
cmake \
|
||||
libasound2-dev \
|
||||
libavcodec-dev \
|
||||
libavdevice-dev \
|
||||
libavfilter-dev \
|
||||
libavformat-dev \
|
||||
libavutil-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libfontconfig-dev \
|
||||
libfreetype6-dev \
|
||||
libgl1-mesa-dev \
|
||||
libjack-jackd2-dev \
|
||||
libjansson-dev \
|
||||
libpulse-dev \
|
||||
libqt5x11extras5-dev \
|
||||
libspeexdsp-dev \
|
||||
libswresample-dev \
|
||||
libswscale-dev \
|
||||
libudev-dev \
|
||||
libv4l-dev \
|
||||
libvlc-dev \
|
||||
libx11-dev \
|
||||
libx264-dev \
|
||||
libxcb-shm0-dev \
|
||||
libxcb-xinerama0-dev \
|
||||
libxcomposite-dev \
|
||||
libxinerama-dev \
|
||||
pkg-config \
|
||||
qtbase5-dev
|
||||
|
||||
# obs-websocket deps
|
||||
apt-get install -y libqt5websockets5-dev
|
||||
|
||||
# Build obs-studio
|
||||
cd /root
|
||||
git clone https://github.com/jp9000/obs-studio ./obs-studio
|
||||
cd obs-studio
|
||||
git checkout 19.0.3
|
||||
mkdir build && cd build
|
||||
cmake -DUNIX_STRUCTURE=1 -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
make -j4
|
||||
make install
|
||||
|
||||
ldconfig
|
8
CI/install-qt-win.cmd
Normal file
8
CI/install-qt-win.cmd
Normal file
@ -0,0 +1,8 @@
|
||||
if not exist %QtBaseDir% (
|
||||
curl -kLO https://cdn-fastly.obsproject.com/downloads/Qt_5.10.1.7z -f --retry 5 -z Qt_5.10.1.7z
|
||||
7z x Qt_5.10.1.7z -o%QtBaseDir%
|
||||
) else (
|
||||
echo "Qt is already installed. Download skipped."
|
||||
)
|
||||
|
||||
dir %QtBaseDir%
|
10
CI/macos/Brewfile
Normal file
10
CI/macos/Brewfile
Normal file
@ -0,0 +1,10 @@
|
||||
tap "akeru-inc/tap"
|
||||
brew "jack"
|
||||
brew "speexdsp"
|
||||
brew "cmake"
|
||||
brew "freetype"
|
||||
brew "fdk-aac"
|
||||
brew "https://gist.githubusercontent.com/DDRBoxman/9c7a2b08933166f4b61ed9a44b242609/raw/ef4de6c587c6bd7f50210eccd5bd51ff08e6de13/qt.rb"
|
||||
brew "swig", link: false
|
||||
brew "https://gist.githubusercontent.com/DDRBoxman/4cada55c51803a2f963fa40ce55c9d3e/raw/572c67e908bfbc1bcb8c476ea77ea3935133f5b5/swig.rb"
|
||||
brew "akeru-inc/tap/xcnotary"
|
@ -12,123 +12,6 @@
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>../../build/QtNetwork</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>292</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>3</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>../../build/QtWebSockets</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>292</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>3</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>bin</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>509</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>2</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>Resources</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>509</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>2</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>Contents</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>509</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>2</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>OBS.app</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>509</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>2</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>Applications</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>509</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
@ -631,11 +514,11 @@
|
||||
<key>CONCLUSION_ACTION</key>
|
||||
<integer>0</integer>
|
||||
<key>IDENTIFIER</key>
|
||||
<string>fr.palakis.obswebsocket</string>
|
||||
<string>fr.palakis.obs-websocket</string>
|
||||
<key>OVERWRITE_PERMISSIONS</key>
|
||||
<false/>
|
||||
<key>VERSION</key>
|
||||
<string>4.0.0</string>
|
||||
<string>4.9.0</string>
|
||||
</dict>
|
||||
<key>PROJECT_COMMENTS</key>
|
||||
<dict>
|
145
CI/macos/qt.rb
Normal file
145
CI/macos/qt.rb
Normal file
@ -0,0 +1,145 @@
|
||||
# Patches for Qt must be at the very least submitted to Qt's Gerrit codereview
|
||||
# rather than their bug-report Jira. The latter is rarely reviewed by Qt.
|
||||
class Qt < Formula
|
||||
desc "Cross-platform application and UI framework"
|
||||
homepage "https://www.qt.io/"
|
||||
url "https://download.qt.io/official_releases/qt/5.10/5.10.1/single/qt-everywhere-src-5.10.1.tar.xz"
|
||||
mirror "https://www.mirrorservice.org/sites/download.qt-project.org/official_releases/qt/5.10/5.10.1/single/qt-everywhere-src-5.10.1.tar.xz"
|
||||
sha256 "05ffba7b811b854ed558abf2be2ddbd3bb6ddd0b60ea4b5da75d277ac15e740a"
|
||||
head "https://code.qt.io/qt/qt5.git", :branch => "5.10", :shallow => false
|
||||
|
||||
bottle do
|
||||
sha256 "8b4bad005596a5f8790150fe455db998ac2406f4e0f04140d6656205d844d266" => :high_sierra
|
||||
sha256 "9c488554935fb573554a4e36d36d3c81e47245b7fefc4b61edef894e67ba1740" => :sierra
|
||||
sha256 "c0407afba5951df6cc4c6f6c1c315972bd41c99cecb4e029919c4c15ab6f7bdc" => :el_capitan
|
||||
end
|
||||
|
||||
keg_only "Qt 5 has CMake issues when linked"
|
||||
|
||||
option "with-docs", "Build documentation"
|
||||
option "with-examples", "Build examples"
|
||||
option "without-proprietary-codecs", "Don't build with proprietary codecs (e.g. mp3)"
|
||||
|
||||
# OS X 10.7 Lion is still supported in Qt 5.5, but is no longer a reference
|
||||
# configuration and thus untested in practice. Builds on OS X 10.7 have been
|
||||
# reported to fail: <https://github.com/Homebrew/homebrew/issues/45284>.
|
||||
# depends_on :macos => :mountain_lion
|
||||
|
||||
depends_on "pkg-config" => :build
|
||||
depends_on :xcode => :build
|
||||
depends_on "mysql" => :optional
|
||||
depends_on "postgresql" => :optional
|
||||
|
||||
# Restore `.pc` files for framework-based build of Qt 5 on OS X. This
|
||||
# partially reverts <https://codereview.qt-project.org/#/c/140954/> merged
|
||||
# between the 5.5.1 and 5.6.0 releases. (Remove this as soon as feasible!)
|
||||
#
|
||||
# Core formulae known to fail without this patch (as of 2016-10-15):
|
||||
# * gnuplot (with `--with-qt` option)
|
||||
# * mkvtoolnix (with `--with-qt` option, silent build failure)
|
||||
# * poppler (with `--with-qt` option)
|
||||
patch do
|
||||
url "https://raw.githubusercontent.com/Homebrew/formula-patches/e8fe6567/qt5/restore-pc-files.patch"
|
||||
sha256 "48ff18be2f4050de7288bddbae7f47e949512ac4bcd126c2f504be2ac701158b"
|
||||
end
|
||||
|
||||
def install
|
||||
args = %W[
|
||||
-verbose
|
||||
-prefix #{prefix}
|
||||
-release
|
||||
-opensource -confirm-license
|
||||
-system-zlib
|
||||
-qt-libpng
|
||||
-qt-libjpeg
|
||||
-qt-freetype
|
||||
-qt-pcre
|
||||
-nomake tests
|
||||
-no-rpath
|
||||
-pkg-config
|
||||
-dbus-runtime
|
||||
]
|
||||
|
||||
args << "-nomake" << "examples" if build.without? "examples"
|
||||
|
||||
if build.with? "mysql"
|
||||
args << "-plugin-sql-mysql"
|
||||
(buildpath/"brew_shim/mysql_config").write <<~EOS
|
||||
#!/bin/sh
|
||||
if [ x"$1" = x"--libs" ]; then
|
||||
mysql_config --libs | sed "s/-lssl -lcrypto//"
|
||||
else
|
||||
exec mysql_config "$@"
|
||||
fi
|
||||
EOS
|
||||
chmod 0755, "brew_shim/mysql_config"
|
||||
args << "-mysql_config" << buildpath/"brew_shim/mysql_config"
|
||||
end
|
||||
|
||||
args << "-plugin-sql-psql" if build.with? "postgresql"
|
||||
args << "-proprietary-codecs" if build.with? "proprietary-codecs"
|
||||
|
||||
system "./configure", *args
|
||||
system "make"
|
||||
ENV.deparallelize
|
||||
system "make", "install"
|
||||
|
||||
if build.with? "docs"
|
||||
system "make", "docs"
|
||||
system "make", "install_docs"
|
||||
end
|
||||
|
||||
# Some config scripts will only find Qt in a "Frameworks" folder
|
||||
frameworks.install_symlink Dir["#{lib}/*.framework"]
|
||||
|
||||
# The pkg-config files installed suggest that headers can be found in the
|
||||
# `include` directory. Make this so by creating symlinks from `include` to
|
||||
# the Frameworks' Headers folders.
|
||||
Pathname.glob("#{lib}/*.framework/Headers") do |path|
|
||||
include.install_symlink path => path.parent.basename(".framework")
|
||||
end
|
||||
|
||||
# Move `*.app` bundles into `libexec` to expose them to `brew linkapps` and
|
||||
# because we don't like having them in `bin`.
|
||||
# (Note: This move breaks invocation of Assistant via the Help menu
|
||||
# of both Designer and Linguist as that relies on Assistant being in `bin`.)
|
||||
libexec.mkpath
|
||||
Pathname.glob("#{bin}/*.app") { |app| mv app, libexec }
|
||||
end
|
||||
|
||||
def caveats; <<~EOS
|
||||
We agreed to the Qt opensource license for you.
|
||||
If this is unacceptable you should uninstall.
|
||||
EOS
|
||||
end
|
||||
|
||||
test do
|
||||
(testpath/"hello.pro").write <<~EOS
|
||||
QT += core
|
||||
QT -= gui
|
||||
TARGET = hello
|
||||
CONFIG += console
|
||||
CONFIG -= app_bundle
|
||||
TEMPLATE = app
|
||||
SOURCES += main.cpp
|
||||
EOS
|
||||
|
||||
(testpath/"main.cpp").write <<~EOS
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
qDebug() << "Hello World!";
|
||||
return 0;
|
||||
}
|
||||
EOS
|
||||
|
||||
system bin/"qmake", testpath/"hello.pro"
|
||||
system "make"
|
||||
assert_predicate testpath/"hello", :exist?
|
||||
assert_predicate testpath/"main.o", :exist?
|
||||
system "./hello"
|
||||
end
|
||||
end
|
90
CI/package-macos.sh
Executable file
90
CI/package-macos.sh
Executable file
@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
OSTYPE=$(uname)
|
||||
|
||||
if [ "${OSTYPE}" != "Darwin" ]; then
|
||||
echo "[obs-websocket - Error] macOS package script can be run on Darwin-type OS only."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[obs-websocket] Preparing package build"
|
||||
export QT_CELLAR_PREFIX="$(/usr/bin/find /usr/local/Cellar/qt -d 1 | sort -t '.' -k 1,1n -k 2,2n -k 3,3n | tail -n 1)"
|
||||
|
||||
GIT_HASH=$(git rev-parse --short HEAD)
|
||||
GIT_BRANCH_OR_TAG=$(git name-rev --name-only HEAD | awk -F/ '{print $NF}')
|
||||
|
||||
VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG"
|
||||
|
||||
FILENAME_UNSIGNED="obs-websocket-$VERSION-Unsigned.pkg"
|
||||
FILENAME="obs-websocket-$VERSION.pkg"
|
||||
|
||||
echo "[obs-websocket] Modifying obs-websocket.so"
|
||||
install_name_tool \
|
||||
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets \
|
||||
@executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \
|
||||
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui \
|
||||
@executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \
|
||||
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore \
|
||||
@executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \
|
||||
./build/obs-websocket.so
|
||||
|
||||
# Check if replacement worked
|
||||
echo "[obs-websocket] Dependencies for obs-websocket"
|
||||
otool -L ./build/obs-websocket.so
|
||||
|
||||
if [[ "$RELEASE_MODE" == "True" ]]; then
|
||||
echo "[obs-websocket] Signing plugin binary: obs-websocket.so"
|
||||
codesign --sign "$CODE_SIGNING_IDENTITY" ./build/obs-websocket.so
|
||||
else
|
||||
echo "[obs-websocket] Skipped plugin codesigning"
|
||||
fi
|
||||
|
||||
echo "[obs-websocket] Actual package build"
|
||||
packagesbuild ./CI/macos/obs-websocket.pkgproj
|
||||
|
||||
echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME"
|
||||
mv ./release/obs-websocket.pkg ./release/$FILENAME_UNSIGNED
|
||||
|
||||
if [[ "$RELEASE_MODE" == "True" ]]; then
|
||||
echo "[obs-websocket] Signing installer: $FILENAME"
|
||||
productsign \
|
||||
--sign "$INSTALLER_SIGNING_IDENTITY" \
|
||||
./release/$FILENAME_UNSIGNED \
|
||||
./release/$FILENAME
|
||||
rm ./release/$FILENAME_UNSIGNED
|
||||
|
||||
echo "[obs-websocket] Submitting installer $FILENAME for notarization"
|
||||
zip -r ./release/$FILENAME.zip ./release/$FILENAME
|
||||
UPLOAD_RESULT=$(xcrun altool \
|
||||
--notarize-app \
|
||||
--primary-bundle-id "fr.palakis.obs-websocket" \
|
||||
--username "$AC_USERNAME" \
|
||||
--password "$AC_PASSWORD" \
|
||||
--asc-provider "$AC_PROVIDER_SHORTNAME" \
|
||||
--file "./release/$FILENAME.zip")
|
||||
rm ./release/$FILENAME.zip
|
||||
|
||||
REQUEST_UUID=$(echo $UPLOAD_RESULT | awk -F ' = ' '/RequestUUID/ {print $2}')
|
||||
echo "Request UUID: $REQUEST_UUID"
|
||||
|
||||
echo "[obs-websocket] Wait for notarization result"
|
||||
# Pieces of code borrowed from rednoah/notarized-app
|
||||
while sleep 30 && date; do
|
||||
CHECK_RESULT=$(xcrun altool \
|
||||
--notarization-info "$REQUEST_UUID" \
|
||||
--username "$AC_USERNAME" \
|
||||
--password "$AC_PASSWORD" \
|
||||
--asc-provider "$AC_PROVIDER_SHORTNAME")
|
||||
echo $CHECK_RESULT
|
||||
|
||||
if ! grep -q "Status: in progress" <<< "$CHECK_RESULT"; then
|
||||
echo "[obs-websocket] Staple ticket to installer: $FILENAME"
|
||||
xcrun stapler staple ./release/$FILENAME
|
||||
break
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "[obs-websocket] Skipped installer codesigning and notarization"
|
||||
fi
|
@ -1,66 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "-- Preparing package build"
|
||||
export QT_CELLAR_PREFIX="$(find /usr/local/Cellar/qt -d 1 | tail -n 1)"
|
||||
|
||||
export WS_LIB="/usr/local/opt/qt/lib/QtWebSockets.framework/QtWebSockets"
|
||||
export NET_LIB="/usr/local/opt/qt/lib/QtNetwork.framework/QtNetwork"
|
||||
|
||||
export GIT_HASH=$(git rev-parse --short HEAD)
|
||||
|
||||
export VERSION="$GIT_HASH-$TRAVIS_BRANCH"
|
||||
export LATEST_VERSION="$TRAVIS_BRANCH"
|
||||
if [ -n "${TRAVIS_TAG}" ]; then
|
||||
export VERSION="$TRAVIS_TAG"
|
||||
export LATEST_VERSION="$TRAVIS_TAG"
|
||||
fi
|
||||
|
||||
export FILENAME="obs-websocket-$VERSION.pkg"
|
||||
export LATEST_FILENAME="obs-websocket-latest-$LATEST_VERSION.pkg"
|
||||
|
||||
echo "-- Copying Qt dependencies"
|
||||
cp $WS_LIB ./build
|
||||
cp $NET_LIB ./build
|
||||
|
||||
chmod +rw ./build/QtWebSockets ./build/QtNetwork
|
||||
|
||||
echo "-- Modifying QtNetwork"
|
||||
install_name_tool \
|
||||
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
|
||||
-change $QT_CELLAR_PREFIX/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
|
||||
./build/QtNetwork
|
||||
|
||||
echo "-- Modifying QtWebSockets"
|
||||
install_name_tool \
|
||||
-change /usr/local/opt/qt/lib/QtWebSockets.framework/Versions/5/QtWebSockets @rpath/QtWebSockets \
|
||||
-change $QT_CELLAR_PREFIX/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
|
||||
-change $QT_CELLAR_PREFIX/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
|
||||
./build/QtWebSockets
|
||||
|
||||
echo "-- Modifying obs-websocket.so"
|
||||
install_name_tool \
|
||||
-change /usr/local/opt/qt/lib/QtWebSockets.framework/Versions/5/QtWebSockets @rpath/QtWebSockets \
|
||||
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets \
|
||||
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
|
||||
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui \
|
||||
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
|
||||
./build/obs-websocket.so
|
||||
|
||||
# Check if replacement worked
|
||||
echo "-- Dependencies for QtNetwork"
|
||||
otool -L ./build/QtNetwork
|
||||
echo "-- Dependencies for QtWebSockets"
|
||||
otool -L ./build/QtWebSockets
|
||||
echo "-- Dependencies for obs-websocket"
|
||||
otool -L ./build/obs-websocket.so
|
||||
|
||||
chmod -w ./build/QtWebSockets ./build/QtNetwork
|
||||
|
||||
echo "-- Actual package build"
|
||||
packagesbuild ./CI/osx/obs-websocket.pkgproj
|
||||
|
||||
echo "-- Renaming obs-websocket.pkg to $FILENAME"
|
||||
mv ./release/obs-websocket.pkg ./release/$FILENAME
|
||||
cp ./release/$FILENAME ./release/$LATEST_FILENAME
|
24
CI/package-ubuntu.sh
Executable file
24
CI/package-ubuntu.sh
Executable file
@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
export GIT_HASH=$(git rev-parse --short HEAD)
|
||||
export PKG_VERSION="1-$GIT_HASH-$BRANCH_SHORT_NAME-git"
|
||||
|
||||
if [[ $BRANCH_FULL_NAME =~ ^refs/tags/ ]]; then
|
||||
export PKG_VERSION="$BRANCH_SHORT_NAME"
|
||||
echo "[obs-websocket] Branch is a tag. Setting version to `$PKG_VERSION`."
|
||||
fi
|
||||
|
||||
cd ./build
|
||||
|
||||
PAGER="cat" sudo checkinstall -y --type=debian --fstrans=no --nodoc \
|
||||
--backup=no --deldoc=yes --install=no \
|
||||
--pkgname=obs-websocket --pkgversion="$PKG_VERSION" \
|
||||
--pkglicense="GPLv2.0" --maintainer="stephane.lepin@gmail.com" \
|
||||
--pkggroup="video" \
|
||||
--pkgsource="https://github.com/Palakis/obs-websocket" \
|
||||
--requires="obs-studio \(\>= 25.0.7\), libqt5core5a, libqt5widgets5, qt5-image-formats-plugins" \
|
||||
--pakdir="../package"
|
||||
|
||||
sudo chmod ao+r ../package/*
|
12
CI/package-windows.cmd
Normal file
12
CI/package-windows.cmd
Normal file
@ -0,0 +1,12 @@
|
||||
mkdir package
|
||||
cd package
|
||||
|
||||
git rev-parse --short HEAD > package-version.txt
|
||||
set /p PackageVersion=<package-version.txt
|
||||
del package-version.txt
|
||||
|
||||
REM Package ZIP archive
|
||||
7z a "obs-websocket-%PackageVersion%-Windows.zip" "..\release\*"
|
||||
|
||||
REM Build installer
|
||||
iscc ..\installer\installer.iss /O. /F"obs-websocket-%PackageVersion%-Windows-Installer"
|
@ -1,24 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
cd /root/obs-websocket
|
||||
|
||||
export GIT_HASH=$(git rev-parse --short HEAD)
|
||||
export PKG_VERSION="1-$GIT_HASH-$TRAVIS_BRANCH-git"
|
||||
|
||||
if [ -n "${TRAVIS_TAG}" ]; then
|
||||
export PKG_VERSION="$TRAVIS_TAG"
|
||||
fi
|
||||
|
||||
cd /root/obs-websocket/build
|
||||
|
||||
PAGER=cat checkinstall -y --type=debian --fstrans=no --nodoc \
|
||||
--backup=no --deldoc=yes --install=no \
|
||||
--pkgname=obs-websocket --pkgversion="$PKG_VERSION" \
|
||||
--pkglicense="GPLv2.0" --maintainer="contact@slepin.fr" \
|
||||
--requires="libqt5websockets5" --pkggroup="video" \
|
||||
--pkgsource="https://github.com/Palakis/obs-websocket" \
|
||||
--pakdir="/package"
|
||||
|
||||
chmod ao+r /package/*
|
37
CI/prepare-obs-windows.cmd
Normal file
37
CI/prepare-obs-windows.cmd
Normal file
@ -0,0 +1,37 @@
|
||||
|
||||
@echo off
|
||||
SETLOCAL EnableDelayedExpansion
|
||||
|
||||
REM If obs-studio directory does not exist, clone the git repo
|
||||
if not exist %OBSPath% (
|
||||
echo obs-studio directory does not exist
|
||||
git clone https://github.com/obsproject/obs-studio %OBSPath%
|
||||
cd /D %OBSPath%\
|
||||
git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\obs-studio-latest-tag.txt"
|
||||
set /p OBSLatestTag=<"%OBSPath%\obs-studio-latest-tag.txt"
|
||||
)
|
||||
|
||||
REM Prepare OBS Studio builds
|
||||
|
||||
echo Running CMake...
|
||||
cd /D %OBSPath%
|
||||
echo git checkout %OBSLatestTag%
|
||||
git checkout %OBSLatestTag%
|
||||
echo:
|
||||
|
||||
if not exist build32 mkdir build32
|
||||
if not exist build64 mkdir build64
|
||||
|
||||
echo Running cmake for obs-studio %OBSLatestTag% 32-bit...
|
||||
cd build32
|
||||
cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DDepsPath="%DepsPath32%" -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||
echo:
|
||||
echo:
|
||||
|
||||
echo Running cmake for obs-studio %OBSLatestTag% 64-bit...
|
||||
cd ..\build64
|
||||
cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DDepsPath="%DepsPath64%" -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||
echo:
|
||||
echo:
|
||||
|
||||
dir "%OBSPath%\libobs"
|
7
CI/prepare-windows.cmd
Normal file
7
CI/prepare-windows.cmd
Normal file
@ -0,0 +1,7 @@
|
||||
mkdir build32
|
||||
mkdir build64
|
||||
|
||||
cd build32
|
||||
cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DLibObs_DIR="%OBSPath%\build32\libobs" -DLIBOBS_INCLUDE_DIR="%OBSPath%\libobs" -DLIBOBS_LIB="%OBSPath%\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="%OBSPath%\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
|
||||
cd ..\build64
|
||||
cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DLibObs_DIR="%OBSPath%\build64\libobs" -DLIBOBS_INCLUDE_DIR="%OBSPath%\libobs" -DLIBOBS_LIB="%OBSPath%\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="%OBSPath%\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
|
180
CMakeLists.txt
180
CMakeLists.txt
@ -1,60 +1,84 @@
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
project(obs-websocket)
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(obs-websocket VERSION 4.9.0)
|
||||
|
||||
set(CMAKE_PREFIX_PATH "${QTDIR}")
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
|
||||
include(external/FindLibObs.cmake)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_definitions(-DASIO_STANDALONE)
|
||||
|
||||
if (${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm")
|
||||
set(CMAKE_CXX_FLAGS "-mfpu=neon")
|
||||
endif()
|
||||
|
||||
if (WIN32 OR APPLE)
|
||||
include(external/FindLibObs.cmake)
|
||||
endif()
|
||||
|
||||
find_package(LibObs REQUIRED)
|
||||
find_package(Qt5Core REQUIRED)
|
||||
find_package(Qt5WebSockets REQUIRED)
|
||||
find_package(Qt5Widgets REQUIRED)
|
||||
find_package(Qt5 REQUIRED COMPONENTS Core Widgets)
|
||||
|
||||
add_subdirectory(deps/mbedtls EXCLUDE_FROM_ALL)
|
||||
set(ENABLE_PROGRAMS false)
|
||||
|
||||
set(obs-websocket_SOURCES
|
||||
obs-websocket.cpp
|
||||
WSServer.cpp
|
||||
WSRequestHandler.cpp
|
||||
WSEvents.cpp
|
||||
Config.cpp
|
||||
Utils.cpp
|
||||
forms/settings-dialog.cpp)
|
||||
set(obs-websocket_SOURCES
|
||||
src/obs-websocket.cpp
|
||||
src/WSServer.cpp
|
||||
src/ConnectionProperties.cpp
|
||||
src/WSRequestHandler.cpp
|
||||
src/WSRequestHandler_General.cpp
|
||||
src/WSRequestHandler_Profiles.cpp
|
||||
src/WSRequestHandler_Recording.cpp
|
||||
src/WSRequestHandler_ReplayBuffer.cpp
|
||||
src/WSRequestHandler_SceneCollections.cpp
|
||||
src/WSRequestHandler_Scenes.cpp
|
||||
src/WSRequestHandler_SceneItems.cpp
|
||||
src/WSRequestHandler_Sources.cpp
|
||||
src/WSRequestHandler_Streaming.cpp
|
||||
src/WSRequestHandler_StudioMode.cpp
|
||||
src/WSRequestHandler_Transitions.cpp
|
||||
src/WSRequestHandler_Outputs.cpp
|
||||
src/WSRequestHandler_MediaControl.cpp
|
||||
src/WSEvents.cpp
|
||||
src/Config.cpp
|
||||
src/Utils.cpp
|
||||
src/rpc/RpcRequest.cpp
|
||||
src/rpc/RpcResponse.cpp
|
||||
src/rpc/RpcEvent.cpp
|
||||
src/protocol/OBSRemoteProtocol.cpp
|
||||
src/forms/settings-dialog.cpp)
|
||||
|
||||
set(obs-websocket_HEADERS
|
||||
obs-websocket.h
|
||||
WSServer.h
|
||||
WSRequestHandler.h
|
||||
WSEvents.h
|
||||
Config.h
|
||||
Utils.h
|
||||
forms/settings-dialog.h)
|
||||
src/obs-websocket.h
|
||||
src/WSServer.h
|
||||
src/ConnectionProperties.h
|
||||
src/WSRequestHandler.h
|
||||
src/WSEvents.h
|
||||
src/Config.h
|
||||
src/Utils.h
|
||||
src/rpc/RpcRequest.h
|
||||
src/rpc/RpcResponse.h
|
||||
src/rpc/RpcEvent.h
|
||||
src/protocol/OBSRemoteProtocol.h
|
||||
src/forms/settings-dialog.h)
|
||||
|
||||
# --- Platform-independent build settings ---
|
||||
add_library(obs-websocket MODULE
|
||||
add_library(obs-websocket MODULE
|
||||
${obs-websocket_SOURCES}
|
||||
${obs-websocket_HEADERS})
|
||||
|
||||
add_dependencies(obs-websocket mbedcrypto)
|
||||
|
||||
include_directories(
|
||||
include_directories(
|
||||
"${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api"
|
||||
${Qt5Core_INCLUDES}
|
||||
${Qt5WebSockets_INCLUDES}
|
||||
${Qt5Widgets_INCLUDES}
|
||||
${mbedcrypto_INCLUDES}
|
||||
"${CMAKE_SOURCE_DIR}/deps/mbedtls/include")
|
||||
"${CMAKE_SOURCE_DIR}/deps/asio/asio/include"
|
||||
"${CMAKE_SOURCE_DIR}/deps/websocketpp")
|
||||
|
||||
target_link_libraries(obs-websocket
|
||||
target_link_libraries(obs-websocket
|
||||
libobs
|
||||
Qt5::Core
|
||||
Qt5::WebSockets
|
||||
Qt5::Widgets
|
||||
mbedcrypto)
|
||||
Qt5::Widgets)
|
||||
|
||||
# --- End of section ---
|
||||
|
||||
@ -62,9 +86,16 @@ target_link_libraries(obs-websocket
|
||||
if(WIN32)
|
||||
if(NOT DEFINED OBS_FRONTEND_LIB)
|
||||
set(OBS_FRONTEND_LIB "OBS_FRONTEND_LIB-NOTFOUND" CACHE FILEPATH "OBS frontend library")
|
||||
message(FATAL_ERROR "Could not find OBS Frontend API\'s library !")
|
||||
message(FATAL_ERROR "Could not find OBS Frontend API's library !")
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
# Enable Multicore Builds and disable FH4 (to not depend on VCRUNTIME140_1.DLL)
|
||||
add_definitions(/MP /d2FH4-)
|
||||
endif()
|
||||
|
||||
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
|
||||
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(ARCH_NAME "64bit")
|
||||
set(OBS_BUILDDIR_ARCH "build64")
|
||||
@ -80,44 +111,62 @@ if(WIN32)
|
||||
target_link_libraries(obs-websocket
|
||||
"${OBS_FRONTEND_LIB}")
|
||||
|
||||
add_custom_command(TARGET obs-websocket POST_BUILD
|
||||
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy
|
||||
"${QTDIR}/bin/Qt5WebSockets.dll"
|
||||
"${QTDIR}/bin/Qt5Network.dll"
|
||||
"${CMAKE_BINARY_DIR}/$<CONFIG>")
|
||||
|
||||
COMMAND if $<CONFIG:Debug>==1 ("${CMAKE_COMMAND}" -E copy
|
||||
"${QTDIR}/bin/Qt5WebSocketsd.dll"
|
||||
"${QTDIR}/bin/Qt5Networkd.dll"
|
||||
"${CMAKE_BINARY_DIR}/$<CONFIG>")
|
||||
)
|
||||
|
||||
# --- Release package helper ---
|
||||
# The "release" folder has a structure similar OBS' one on Windows
|
||||
set(RELEASE_DIR "${PROJECT_SOURCE_DIR}/release")
|
||||
|
||||
add_custom_command(TARGET obs-websocket POST_BUILD
|
||||
# If config is Release, package release files
|
||||
COMMAND if $<CONFIG:Release>==1 (
|
||||
"${CMAKE_COMMAND}" -E make_directory
|
||||
"${RELEASE_DIR}/data/obs-plugins/obs-websocket"
|
||||
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
||||
|
||||
|
||||
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy_directory
|
||||
"${PROJECT_SOURCE_DIR}/data"
|
||||
"${RELEASE_DIR}/data/obs-plugins/obs-websocket")
|
||||
|
||||
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy
|
||||
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy
|
||||
"$<TARGET_FILE:obs-websocket>"
|
||||
"${QTDIR}/bin/Qt5WebSockets.dll"
|
||||
"${QTDIR}/bin/Qt5Network.dll"
|
||||
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
||||
|
||||
# In Release mode, copy Qt image format plugins
|
||||
COMMAND if $<CONFIG:Release>==1 (
|
||||
"${CMAKE_COMMAND}" -E copy
|
||||
"${QTDIR}/plugins/imageformats/qjpeg.dll"
|
||||
"${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll")
|
||||
COMMAND if $<CONFIG:RelWithDebInfo>==1 (
|
||||
"${CMAKE_COMMAND}" -E copy
|
||||
"${QTDIR}/plugins/imageformats/qjpeg.dll"
|
||||
"${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll")
|
||||
|
||||
# If config is RelWithDebInfo, package release files
|
||||
COMMAND if $<CONFIG:RelWithDebInfo>==1 (
|
||||
"${CMAKE_COMMAND}" -E make_directory
|
||||
"${RELEASE_DIR}/data/obs-plugins/obs-websocket"
|
||||
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
||||
|
||||
COMMAND if $<CONFIG:RelWithDebInfo>==1 ("${CMAKE_COMMAND}" -E copy_directory
|
||||
"${PROJECT_SOURCE_DIR}/data"
|
||||
"${RELEASE_DIR}/data/obs-plugins/obs-websocket")
|
||||
|
||||
COMMAND if $<CONFIG:RelWithDebInfo>==1 ("${CMAKE_COMMAND}" -E copy
|
||||
"$<TARGET_FILE:obs-websocket>"
|
||||
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
||||
|
||||
COMMAND if $<CONFIG:RelWithDebInfo>==1 ("${CMAKE_COMMAND}" -E copy
|
||||
"$<TARGET_PDB_FILE:obs-websocket>"
|
||||
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
||||
|
||||
# Copy to obs-studio dev environment for immediate testing
|
||||
COMMAND if $<CONFIG:Debug>==1 (
|
||||
"${CMAKE_COMMAND}" -E copy
|
||||
"$<TARGET_FILE:obs-websocket>"
|
||||
"${QTDIR}/bin/Qt5WebSocketsd.dll"
|
||||
"${QTDIR}/bin/Qt5Networkd.dll"
|
||||
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/obs-plugins/${ARCH_NAME}")
|
||||
|
||||
COMMAND if $<CONFIG:Debug>==1 (
|
||||
"${CMAKE_COMMAND}" -E copy
|
||||
"$<TARGET_PDB_FILE:obs-websocket>"
|
||||
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/obs-plugins/${ARCH_NAME}")
|
||||
|
||||
COMMAND if $<CONFIG:Debug>==1 (
|
||||
@ -136,19 +185,27 @@ endif()
|
||||
|
||||
# --- Linux-specific build settings and tasks ---
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
include(GNUInstallDirs)
|
||||
|
||||
target_compile_options(mbedcrypto PRIVATE -fPIC)
|
||||
set_target_properties(obs-websocket PROPERTIES PREFIX "")
|
||||
target_link_libraries(obs-websocket
|
||||
obs-frontend-api)
|
||||
target_link_libraries(obs-websocket obs-frontend-api)
|
||||
|
||||
file(GLOB locale_files data/locale/*.ini)
|
||||
|
||||
install(TARGETS obs-websocket
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/obs-plugins")
|
||||
set(CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS
|
||||
OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_WRITE GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
|
||||
if(${USE_UBUNTU_FIX})
|
||||
install(TARGETS obs-websocket LIBRARY
|
||||
DESTINATION "/usr/lib/obs-plugins"
|
||||
PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
|
||||
endif()
|
||||
install(TARGETS obs-websocket LIBRARY
|
||||
DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/obs-plugins"
|
||||
PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
|
||||
|
||||
install(FILES ${locale_files}
|
||||
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/obs-websocket/locale")
|
||||
DESTINATION "${CMAKE_INSTALL_FULL_DATAROOTDIR}/obs/obs-plugins/obs-websocket/locale")
|
||||
endif()
|
||||
# --- End of section ---
|
||||
|
||||
@ -156,6 +213,7 @@ endif()
|
||||
if(APPLE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fvisibility=default")
|
||||
|
||||
set(CMAKE_SKIP_RPATH TRUE)
|
||||
set_target_properties(obs-websocket PROPERTIES PREFIX "")
|
||||
target_link_libraries(obs-websocket "${OBS_FRONTEND_LIB}")
|
||||
endif()
|
||||
|
82
CONTRIBUTING.md
Normal file
82
CONTRIBUTING.md
Normal file
@ -0,0 +1,82 @@
|
||||
# Contributing to obs-websocket
|
||||
|
||||
## Translating obs-websocket to your language
|
||||
|
||||
Localization happens on [Crowdin](https://crowdin.com/project/obs-websocket)
|
||||
|
||||
## Branches
|
||||
|
||||
**Development happens on `4.x-current`**
|
||||
|
||||
## Writing code for obs-websocket
|
||||
|
||||
### Code Formatting Guidelines
|
||||
|
||||
* Function and variable names: snake_case for C names, camelCase for C++ method names
|
||||
|
||||
* Request and Event names should use MixedCaps names
|
||||
|
||||
* Request and Event json properties should use camelCase. For more detailed info on property naming, see [Google's JSON Style Guide](https://google.github.io/styleguide/jsoncstyleguide.xml)
|
||||
|
||||
* Code is indented with Tabs. Assume they are 8 columns wide
|
||||
|
||||
* 80 columns max code width. (Docs can be larger)
|
||||
|
||||
* New and updated requests/events must always come with accompanying documentation comments (see existing protocol elements for examples).
|
||||
These are required to automatically generate the [protocol specification document](docs/generated/protocol.md).
|
||||
|
||||
### Code Best-Practices
|
||||
|
||||
* Favor return-early code and avoid wrapping huge portions of code in conditionals. As an example, this:
|
||||
```cpp
|
||||
if (success) {
|
||||
return req->SendOKResponse();
|
||||
} else {
|
||||
return req->SendErrorResponse("something went wrong");
|
||||
}
|
||||
```
|
||||
is better like this:
|
||||
```cpp
|
||||
if (!success) {
|
||||
return req->SendErrorResponse("something went wrong");
|
||||
}
|
||||
return req->SendOKResponse();
|
||||
```
|
||||
|
||||
* Some example common response/request property names are:
|
||||
* `sceneName` - The name of a scene
|
||||
* `sourceName` - The name of a source
|
||||
* `fromScene` - From a scene - scene name
|
||||
|
||||
### Commit Guidelines
|
||||
|
||||
* Commits follow the 50/72 standard:
|
||||
* 50 characters max for the commit title (excluding scope name)
|
||||
* One empty line after the title
|
||||
* Description wrapped to 72 columns max width per line.
|
||||
|
||||
* Commit titles:
|
||||
* Use present tense
|
||||
* Prefix the title with a "scope" name
|
||||
* e.g: "CI: fix wrong behaviour when packaging for OS X"
|
||||
* Typical scopes: CI, General, Requests, Events, Server
|
||||
|
||||
**Example commit:**
|
||||
|
||||
```
|
||||
Requests: Add GetTransitionPosition
|
||||
|
||||
Adds a new request called `GetTransitionPosition` which gets the current
|
||||
transition's state from 0.0f to 1.0f. Works with both auto and manual
|
||||
transitions.
|
||||
```
|
||||
|
||||
### Pull Requests
|
||||
|
||||
* Pull Requests must never be based off your fork's main branch (in this case, `4.x-current`).
|
||||
* Start your work in a newly named branch based on the upstream main one (e.g.: `feature/cool-new-feature`, `bugfix/fix-palakis-mistakes`, ...)
|
||||
|
||||
* Only open a pull request if you are ready to show off your work.
|
||||
|
||||
* If your work is not done yet, but for any reason you need to PR it (like collecting discussions, testing with CI, getting testers),
|
||||
create it as a Draft Pull Request (open the little arrow menu next to the "Create pull request" button, then select "Create draft pull request").
|
184
Config.cpp
184
Config.cpp
@ -1,184 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <mbedtls/base64.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <util/config-file.h>
|
||||
#include <string>
|
||||
|
||||
#define SECTION_NAME "WebsocketAPI"
|
||||
#define PARAM_ENABLE "ServerEnabled"
|
||||
#define PARAM_PORT "ServerPort"
|
||||
#define PARAM_DEBUG "DebugEnabled"
|
||||
#define PARAM_AUTHREQUIRED "AuthRequired"
|
||||
#define PARAM_SECRET "AuthSecret"
|
||||
#define PARAM_SALT "AuthSalt"
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
Config* Config::_instance = new Config();
|
||||
|
||||
Config::Config() :
|
||||
ServerEnabled(true),
|
||||
ServerPort(4444),
|
||||
DebugEnabled(false),
|
||||
AuthRequired(false),
|
||||
Secret(""),
|
||||
Salt(""),
|
||||
SettingsLoaded(false) {
|
||||
// OBS Config defaults
|
||||
config_t* obs_config = obs_frontend_get_global_config();
|
||||
if (obs_config) {
|
||||
config_set_default_bool(obs_config,
|
||||
SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||
config_set_default_uint(obs_config,
|
||||
SECTION_NAME, PARAM_PORT, ServerPort);
|
||||
|
||||
config_set_default_bool(obs_config,
|
||||
SECTION_NAME, PARAM_DEBUG, DebugEnabled);
|
||||
|
||||
config_set_default_bool(obs_config,
|
||||
SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||
config_set_default_string(obs_config,
|
||||
SECTION_NAME, PARAM_SECRET, Secret);
|
||||
config_set_default_string(obs_config,
|
||||
SECTION_NAME, PARAM_SALT, Salt);
|
||||
}
|
||||
|
||||
mbedtls_entropy_init(&entropy);
|
||||
mbedtls_ctr_drbg_init(&rng);
|
||||
mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &entropy, nullptr, 0);
|
||||
|
||||
SessionChallenge = GenerateSalt();
|
||||
}
|
||||
|
||||
Config::~Config() {
|
||||
mbedtls_ctr_drbg_free(&rng);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
}
|
||||
|
||||
void Config::Load() {
|
||||
config_t* obs_config = obs_frontend_get_global_config();
|
||||
|
||||
ServerEnabled = config_get_bool(obs_config, SECTION_NAME, PARAM_ENABLE);
|
||||
ServerPort = config_get_uint(obs_config, SECTION_NAME, PARAM_PORT);
|
||||
|
||||
DebugEnabled = config_get_bool(obs_config, SECTION_NAME, PARAM_DEBUG);
|
||||
|
||||
AuthRequired = config_get_bool(obs_config, SECTION_NAME, PARAM_AUTHREQUIRED);
|
||||
Secret = config_get_string(obs_config, SECTION_NAME, PARAM_SECRET);
|
||||
Salt = config_get_string(obs_config, SECTION_NAME, PARAM_SALT);
|
||||
}
|
||||
|
||||
void Config::Save() {
|
||||
config_t* obs_config = obs_frontend_get_global_config();
|
||||
|
||||
config_set_bool(obs_config, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||
config_set_uint(obs_config, SECTION_NAME, PARAM_PORT, ServerPort);
|
||||
|
||||
config_set_bool(obs_config, SECTION_NAME, PARAM_DEBUG, DebugEnabled);
|
||||
|
||||
config_set_bool(obs_config, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||
config_set_string(obs_config, SECTION_NAME, PARAM_SECRET, Secret);
|
||||
config_set_string(obs_config, SECTION_NAME, PARAM_SALT, Salt);
|
||||
|
||||
config_save(obs_config);
|
||||
}
|
||||
|
||||
const char* Config::GenerateSalt() {
|
||||
// Generate 32 random chars
|
||||
unsigned char* random_chars = (unsigned char*)bzalloc(32);
|
||||
mbedtls_ctr_drbg_random(&rng, random_chars, 32);
|
||||
|
||||
// Convert the 32 random chars to a base64 string
|
||||
char* salt = (char*)bzalloc(64);
|
||||
size_t salt_bytes;
|
||||
mbedtls_base64_encode(
|
||||
(unsigned char*)salt, 64, &salt_bytes,
|
||||
random_chars, 32);
|
||||
|
||||
bfree(random_chars);
|
||||
return salt;
|
||||
}
|
||||
|
||||
const char* Config::GenerateSecret(const char* password, const char* salt) {
|
||||
// Concatenate the password and the salt
|
||||
std::string passAndSalt = "";
|
||||
passAndSalt += password;
|
||||
passAndSalt += salt;
|
||||
|
||||
// Generate a SHA256 hash of the password
|
||||
unsigned char* challengeHash = (unsigned char*)bzalloc(32);
|
||||
mbedtls_sha256(
|
||||
(unsigned char*)passAndSalt.c_str(), passAndSalt.length(),
|
||||
challengeHash, 0);
|
||||
|
||||
// Encode SHA256 hash to Base64
|
||||
char* challenge = (char*)bzalloc(64);
|
||||
size_t challenge_bytes = 0;
|
||||
mbedtls_base64_encode(
|
||||
(unsigned char*)challenge, 64, &challenge_bytes,
|
||||
challengeHash, 32);
|
||||
|
||||
bfree(challengeHash);
|
||||
return challenge;
|
||||
}
|
||||
|
||||
void Config::SetPassword(const char* password) {
|
||||
const char* new_salt = GenerateSalt();
|
||||
const char* new_challenge = GenerateSecret(password, new_salt);
|
||||
|
||||
this->Salt = new_salt;
|
||||
this->Secret = new_challenge;
|
||||
}
|
||||
|
||||
bool Config::CheckAuth(const char* response) {
|
||||
// Concatenate auth secret with the challenge sent to the user
|
||||
std::string challengeAndResponse = "";
|
||||
challengeAndResponse += this->Secret;
|
||||
challengeAndResponse += this->SessionChallenge;
|
||||
|
||||
// Generate a SHA256 hash of challengeAndResponse
|
||||
unsigned char* hash = (unsigned char*)bzalloc(32);
|
||||
mbedtls_sha256(
|
||||
(unsigned char*)challengeAndResponse.c_str(),
|
||||
challengeAndResponse.length(),
|
||||
hash, 0);
|
||||
|
||||
// Encode the SHA256 hash to Base64
|
||||
char* expected_response = (char*)bzalloc(64);
|
||||
size_t base64_size = 0;
|
||||
mbedtls_base64_encode(
|
||||
(unsigned char*)expected_response, 64, &base64_size,
|
||||
hash, 32);
|
||||
|
||||
bool authSuccess = false;
|
||||
if (strcmp(expected_response, response) == 0) {
|
||||
SessionChallenge = GenerateSalt();
|
||||
authSuccess = true;
|
||||
}
|
||||
|
||||
bfree(hash);
|
||||
bfree(expected_response);
|
||||
return authSuccess;
|
||||
}
|
||||
|
||||
Config* Config::Current() {
|
||||
return _instance;
|
||||
}
|
57
Config.h
57
Config.h
@ -1,57 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
|
||||
class Config {
|
||||
public:
|
||||
Config();
|
||||
~Config();
|
||||
void Load();
|
||||
void Save();
|
||||
|
||||
void SetPassword(const char* password);
|
||||
bool CheckAuth(const char* userChallenge);
|
||||
const char* GenerateSalt();
|
||||
static const char* GenerateSecret(
|
||||
const char* password, const char* salt);
|
||||
|
||||
bool ServerEnabled;
|
||||
uint64_t ServerPort;
|
||||
|
||||
bool DebugEnabled;
|
||||
|
||||
bool AuthRequired;
|
||||
const char* Secret;
|
||||
const char* Salt;
|
||||
const char* SessionChallenge;
|
||||
bool SettingsLoaded;
|
||||
|
||||
static Config* Current();
|
||||
|
||||
private:
|
||||
static Config* _instance;
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_ctr_drbg_context rng;
|
||||
};
|
||||
|
||||
#endif // CONFIG_H
|
118
README.md
118
README.md
@ -1,70 +1,132 @@
|
||||
obs-websocket
|
||||
==============
|
||||
Remote control of OBS Studio made easy.
|
||||
# obs-websocket
|
||||
|
||||
Follow the project on Twitter for news & updates : [@obswebsocket](https://twitter.com/obswebsocket)
|
||||
<p align="center">
|
||||
<img src="/.github/images/obsws_logo.png" width=150 align="center">
|
||||
</p>
|
||||
|
||||
[](https://gitter.im/obs-websocket/obs-websocket) [](https://ci.appveyor.com/project/Palakis/obs-websocket/history) [](https://travis-ci.org/Palakis/obs-websocket)
|
||||
WebSockets API for OBS Studio.
|
||||
|
||||
[](https://dev.azure.com/Palakis/obs-websocket/_build/latest?definitionId=2&branchName=4.x-current)
|
||||
[](https://www.codefactor.io/repository/github/palakis/obs-websocket)
|
||||
[](https://twitter.com/LePalakis)
|
||||
[](https://discord.gg/WBaSQ3A)
|
||||
[](https://opencollective.com/obs-websocket)
|
||||
|
||||
## Downloads
|
||||
Binaries for Windows and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
|
||||
|
||||
Binaries for Windows, MacOS, and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
|
||||
|
||||
### Homebrew
|
||||
|
||||
If you're using MacOS you can use Homebrew for installation as well:
|
||||
|
||||
```sh
|
||||
brew install obs-websocket
|
||||
```
|
||||
|
||||
## Using obs-websocket
|
||||
A web client and frontend made by [t2t2](https://github.com/t2t2/obs-tablet-remote) (compatible with tablets and other touch interfaces) is available here : http://t2t2.github.io/obs-tablet-remote/
|
||||
|
||||
Here is a list of available web clients: (compatible with tablets and other touch interfaces)
|
||||
|
||||
- [Niek/obs-web](https://github.com/Niek/obs-web)
|
||||
- [t2t2/obs-tablet-remote](https://github.com/t2t2/obs-tablet-remote)
|
||||
|
||||
It is **highly recommended** to protect obs-websocket with a password against unauthorized control. To do this, open the "Websocket server settings" dialog under OBS' "Tools" menu. In the settings dialogs, you can enable or disable authentication and set a password for it.
|
||||
|
||||
### Possible use cases
|
||||
|
||||
- Remote control OBS from a phone or tablet on the same local network
|
||||
- Change your stream overlay/graphics based on the current scene (like the AGDQ overlay does)
|
||||
- Change your stream overlay/graphics based on the current scene
|
||||
- Automate scene switching with a third-party program (e.g. : auto-pilot, foot pedal, ...)
|
||||
|
||||
### For developers
|
||||
The server is a typical Websockets server running by default on port 4444 (the port number can be changed in the Settings dialog).
|
||||
The protocol understood by the server is documented in [PROTOCOL.md](docs/generated/protocol.md).
|
||||
|
||||
The server is a typical Websockets server running by default on port 4444 (the port number can be changed in the Settings dialog).
|
||||
The protocol understood by the server is documented in [PROTOCOL.md](docs/generated/protocol.md).
|
||||
|
||||
Here's a list of available language APIs for obs-websocket :
|
||||
- Javascript (browser & nodejs): [obs-websocket-js](https://github.com/haganbmj/obs-websocket-js) by Brendan Hagan
|
||||
- C#/VB.NET: [obs-websocket-dotnet](https://github.com/Palakis/obs-websocket-dotnet)
|
||||
- Python 2 and 3: [obs-websocket-py](https://github.com/Elektordi/obs-websocket-py) by Guillaume Genty a.k.a Elektordi
|
||||
- Python 3.5+ with asyncio: [obs-ws-rc](https://github.com/KirillMysnik/obs-ws-rc) by Kirill Mysnik
|
||||
- Python 3.6+ with asyncio: [simpleobsws](https://github.com/IRLToolkit/simpleobsws) by tt2468
|
||||
- Java 8+: [obs-websocket-java](https://github.com/Twasi/websocket-obs-java) by TwasiNET
|
||||
- Java 11+: [obs-java-client](https://github.com/harm27/obs-java-client) by harm27
|
||||
- Golang: [go-obs-websocket](https://github.com/christopher-dG/go-obs-websocket) by Chris de Graaf
|
||||
- Rust: [obws](https://github.com/dnaka91/obws) by dnaka91
|
||||
- HTTP API: [obs-websocket-http](https://github.com/IRLToolkit/obs-websocket-http) by tt2468
|
||||
- CLI: [obs-cli](https://github.com/leafac/obs-cli) by leafac
|
||||
|
||||
I'd like to know what you're building with or for obs-websocket. If you do something in this fashion, feel free to drop me an email at `contact at slepin dot fr` !
|
||||
I'd like to know what you're building with or for obs-websocket. If you do something in this fashion, feel free to drop a message in `#project-showoff` in the [discord server!](https://discord.gg/WBaSQ3A)
|
||||
|
||||
### Securing obs-websocket (via TLS/SSL)
|
||||
|
||||
If you are intending to use obs-websocket outside of a LAN environment, it is highly recommended to secure the connection using a tunneling service.
|
||||
|
||||
See the SSL [tunnelling guide](SSL-TUNNELLING.md) for easy instructions on how to encrypt your websocket connection.
|
||||
|
||||
## Compiling obs-websocket
|
||||
|
||||
See the [build instructions](BUILDING.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
See [the contributing document](/CONTRIBUTING.md)
|
||||
|
||||
## Translations
|
||||
**We need your help on translations**. Please join the localization project on Crowdin: https://crowdin.com/project/obs-websocket
|
||||
|
||||
**Your help is welcome on translations.**
|
||||
|
||||
Please join the localization project on [Crowdin](https://crowdin.com/project/obs-websocket)
|
||||
|
||||
## Special thanks
|
||||
In order of appearance:
|
||||
- [Brendan H.](https://github.com/haganbmj) : Code contributions and gooder English in the Protocol specification
|
||||
- [Mikhail Swift](https://github.com/mikhailswift) : Code contributions
|
||||
- [Tobias Frahmer](https://github.com/Frahmer) : German translation
|
||||
- [Genture](https://github.com/Genteure) : Simplified Chinese and Traditional Chinese translations
|
||||
- [Larissa Gabilan](https://github.com/laris151) : Portuguese translation
|
||||
- [Andy Asquelt](https://github.com/asquelt) : Polish translation
|
||||
- [Marcel Haazen](https://github.com/inpothet) : Dutch translation
|
||||
- [Peter Antonvich](https://github.com/pantonvich) : Code contributions
|
||||
- [yinzara](https://github.com/yinzara) : Code contributions
|
||||
- [Chris Angelico](https://github.com/Rosuav) : Code contributions
|
||||
- [Guillaume "Elektordi" Genty](https://github.com/Elektordi) : Code contributions
|
||||
- [Marwin M](https://github.com/dragonbane0) : Code contributions
|
||||
|
||||
Thank you so much to all of the contibutors [(here)](https://github.com/Palakis/obs-websocket/graphs/contributors) for your amazing help.
|
||||
|
||||
And also: special thanks to supporters of the project!
|
||||
|
||||
## Supporters
|
||||
They have contributed financially to the project and made possible the addition of several features into obs-websocket. Many thanks to them!
|
||||
|
||||
These supporters have contributed financially to the project and made possible the addition of several features into obs-websocket. Many thanks to them!
|
||||
|
||||
---
|
||||
|
||||
[Support Class](http://supportclass.net) designs and develops professional livestreams, with services ranging from broadcast graphics design and integration to event organization, along many other skills.
|
||||
[Support Class](http://supportclass.net) designs and develops professional livestreams, with services ranging from broadcast graphics design and integration to event organization, along many other skills.
|
||||
|
||||
[](http://supportclass.net)
|
||||
|
||||
---
|
||||
|
||||
[MediaUnit](http://www.mediaunit.no) is a Norwegian media company developing products and services for the media industry, primarly focused on web and events.
|
||||
[MediaUnit](http://www.mediaunit.no) is a Norwegian media company developing products and services for the media industry, primarly focused on web and events.
|
||||
|
||||
[](http://www.mediaunit.no/)
|
||||
|
||||
## Contributors
|
||||
|
||||
### Code Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
|
||||
<a href="https://github.com/Palakis/obs-websocket/graphs/contributors"><img src="https://opencollective.com/obs-websocket/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
### Financial Contributors
|
||||
|
||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/obs-websocket/contribute)]
|
||||
|
||||
#### Individuals
|
||||
|
||||
<a href="https://opencollective.com/obs-websocket"><img src="https://opencollective.com/obs-websocket/individuals.svg?width=890"></a>
|
||||
|
||||
#### Organizations
|
||||
|
||||
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/obs-websocket/contribute)]
|
||||
|
||||
<a href="https://opencollective.com/obs-websocket/organization/0/website"><img src="https://opencollective.com/obs-websocket/organization/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/obs-websocket/organization/1/website"><img src="https://opencollective.com/obs-websocket/organization/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/obs-websocket/organization/2/website"><img src="https://opencollective.com/obs-websocket/organization/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/obs-websocket/organization/3/website"><img src="https://opencollective.com/obs-websocket/organization/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/obs-websocket/organization/4/website"><img src="https://opencollective.com/obs-websocket/organization/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/obs-websocket/organization/5/website"><img src="https://opencollective.com/obs-websocket/organization/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/obs-websocket/organization/6/website"><img src="https://opencollective.com/obs-websocket/organization/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/obs-websocket/organization/7/website"><img src="https://opencollective.com/obs-websocket/organization/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/obs-websocket/organization/8/website"><img src="https://opencollective.com/obs-websocket/organization/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/obs-websocket/organization/9/website"><img src="https://opencollective.com/obs-websocket/organization/9/avatar.svg"></a>
|
||||
|
45
SSL-TUNNELLING.md
Normal file
45
SSL-TUNNELLING.md
Normal file
@ -0,0 +1,45 @@
|
||||
# Connecting over a TLS/secure connection (or remotely)
|
||||
|
||||
If you want to expose the WebSocket server of obs-websocket over a secure TLS connection (or to connect remotely), the easiest approach is to use a localhost tunneling service like [ngrok](https://ngrok.com/) or [pagekite](https://pagekite.net/).
|
||||
|
||||
**Before doing this, secure the WebSocket server first by enabling authentication with a strong password!**
|
||||
|
||||
**Please bear in mind that doing this will expose your OBS instance to the open Internet and the security risks it implies. *You've been warned!***
|
||||
|
||||
|
||||
## ngrok
|
||||
|
||||
[Install the ngrok CLI tool](https://ngrok.com/download) on a linux OS, then start ngrok bound to port 4444 like this:
|
||||
|
||||
```bash
|
||||
ngrok http 4444
|
||||
```
|
||||
|
||||
The ngrok command will output something like this:
|
||||
|
||||
```text
|
||||
ngrok by @inconshreveable
|
||||
|
||||
Tunnel Status online
|
||||
Version 2.0/2.0
|
||||
Web Interface http://127.0.0.1:4040
|
||||
Forwarding http://TUNNEL_ID.ngrok.io -> localhost:4444
|
||||
Forwarding https://TUNNEL_ID.ngrok.io -> localhost:4444
|
||||
```
|
||||
|
||||
Where `TUNNEL_ID` is, as the name implies, the unique name of your ngrok tunnel. You'll get a new one every time you start ngrok.
|
||||
|
||||
Then, use `wss://TUNNEL_ID.ngrok.io` to connect to obs-websocket over TLS.
|
||||
|
||||
See the [ngrok documentation](https://ngrok.com/docs) for more tunneling options and settings.
|
||||
|
||||
|
||||
## PageKite
|
||||
|
||||
[Install the PageKite CLI tool](http://pagekite.net/downloads), then start PageKite bound to port 4444 like this (replace NAME with one of your choosing):
|
||||
|
||||
```bash
|
||||
python pagekite.py 4444 NAME.pagekite.me
|
||||
```
|
||||
|
||||
Then, use `wss://NAME.pagekite.me` to connect to obs-websocket over TLS.
|
531
Utils.cpp
531
Utils.cpp
@ -1,531 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QDir>
|
||||
#include <QUrl>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <obs.hpp>
|
||||
#include "obs-websocket.h"
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
Q_DECLARE_METATYPE(OBSScene);
|
||||
|
||||
obs_data_array_t* string_list_to_array(char** strings, char* key) {
|
||||
if (!strings)
|
||||
return obs_data_array_create();
|
||||
|
||||
obs_data_array_t* list = obs_data_array_create();
|
||||
|
||||
char* value = "";
|
||||
for (int i = 0; value != nullptr; i++) {
|
||||
value = strings[i];
|
||||
|
||||
obs_data_t* item = obs_data_create();
|
||||
obs_data_set_string(item, key, value);
|
||||
|
||||
if (value)
|
||||
obs_data_array_push_back(list, item);
|
||||
|
||||
obs_data_release(item);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetSceneItems(obs_source_t* source) {
|
||||
obs_data_array_t* items = obs_data_array_create();
|
||||
obs_scene_t* scene = obs_scene_from_source(source);
|
||||
|
||||
if (!scene)
|
||||
return nullptr;
|
||||
|
||||
obs_scene_enum_items(scene, [](
|
||||
obs_scene_t* scene,
|
||||
obs_sceneitem_t* currentItem,
|
||||
void* param)
|
||||
{
|
||||
obs_data_array_t* data = static_cast<obs_data_array_t*>(param);
|
||||
|
||||
obs_data_t* item_data = GetSceneItemData(currentItem);
|
||||
obs_data_array_insert(data, 0, item_data);
|
||||
|
||||
obs_data_release(item_data);
|
||||
return true;
|
||||
}, items);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
|
||||
if (!item)
|
||||
return nullptr;
|
||||
|
||||
vec2 pos;
|
||||
obs_sceneitem_get_pos(item, &pos);
|
||||
|
||||
vec2 scale;
|
||||
obs_sceneitem_get_scale(item, &scale);
|
||||
|
||||
obs_source_t* item_source = obs_sceneitem_get_source(item);
|
||||
float item_width = float(obs_source_get_width(item_source));
|
||||
float item_height = float(obs_source_get_height(item_source));
|
||||
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_string(data, "name",
|
||||
obs_source_get_name(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_string(data, "type",
|
||||
obs_source_get_id(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_double(data, "volume",
|
||||
obs_source_get_volume(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_double(data, "x", pos.x);
|
||||
obs_data_set_double(data, "y", pos.y);
|
||||
obs_data_set_int(data, "source_cx", (int)item_width);
|
||||
obs_data_set_int(data, "source_cy", (int)item_height);
|
||||
obs_data_set_double(data, "cx", item_width* scale.x);
|
||||
obs_data_set_double(data, "cy", item_height* scale.y);
|
||||
obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, const char* name) {
|
||||
struct current_search {
|
||||
const char* query;
|
||||
obs_sceneitem_t* result;
|
||||
};
|
||||
|
||||
current_search search;
|
||||
search.query = name;
|
||||
search.result = nullptr;
|
||||
|
||||
obs_scene_t* scene = obs_scene_from_source(source);
|
||||
if (scene == nullptr)
|
||||
return nullptr;
|
||||
|
||||
obs_scene_enum_items(scene, [](
|
||||
obs_scene_t* scene,
|
||||
obs_sceneitem_t* currentItem,
|
||||
void* param)
|
||||
{
|
||||
current_search* search = static_cast<current_search*>(param);
|
||||
|
||||
const char* currentItemName =
|
||||
obs_source_get_name(obs_sceneitem_get_source(currentItem));
|
||||
|
||||
if (strcmp(currentItemName, search->query) == 0) {
|
||||
search->result = currentItem;
|
||||
obs_sceneitem_addref(search->result);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, &search);
|
||||
|
||||
return search.result;
|
||||
}
|
||||
|
||||
obs_source_t* Utils::GetTransitionFromName(const char* search_name) {
|
||||
obs_source_t* found_transition = NULL;
|
||||
|
||||
obs_frontend_source_list transition_list = {};
|
||||
obs_frontend_get_transitions(&transition_list);
|
||||
|
||||
for (size_t i = 0; i < transition_list.sources.num; i++) {
|
||||
obs_source_t* transition = transition_list.sources.array[i];
|
||||
|
||||
const char* transition_name = obs_source_get_name(transition);
|
||||
if (strcmp(transition_name, search_name) == 0)
|
||||
{
|
||||
found_transition = transition;
|
||||
obs_source_addref(found_transition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
obs_frontend_source_list_free(&transition_list);
|
||||
|
||||
return found_transition;
|
||||
}
|
||||
|
||||
obs_source_t* Utils::GetSceneFromNameOrCurrent(const char* scene_name) {
|
||||
obs_source_t* scene = nullptr;
|
||||
|
||||
if (!scene_name || !strlen(scene_name))
|
||||
scene = obs_frontend_get_current_scene();
|
||||
else
|
||||
scene = obs_get_source_by_name(scene_name);
|
||||
|
||||
return scene;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetScenes() {
|
||||
obs_frontend_source_list sceneList = {};
|
||||
obs_frontend_get_scenes(&sceneList);
|
||||
|
||||
obs_data_array_t* scenes = obs_data_array_create();
|
||||
for (size_t i = 0; i < sceneList.sources.num; i++) {
|
||||
obs_source_t* scene = sceneList.sources.array[i];
|
||||
|
||||
obs_data_t* scene_data = GetSceneData(scene);
|
||||
obs_data_array_push_back(scenes, scene_data);
|
||||
|
||||
obs_data_release(scene_data);
|
||||
}
|
||||
|
||||
obs_frontend_source_list_free(&sceneList);
|
||||
|
||||
return scenes;
|
||||
}
|
||||
|
||||
obs_data_t* Utils::GetSceneData(obs_source* source) {
|
||||
obs_data_array_t* scene_items = GetSceneItems(source);
|
||||
|
||||
obs_data_t* sceneData = obs_data_create();
|
||||
obs_data_set_string(sceneData, "name", obs_source_get_name(source));
|
||||
obs_data_set_array(sceneData, "sources", scene_items);
|
||||
|
||||
obs_data_array_release(scene_items);
|
||||
return sceneData;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetSceneCollections() {
|
||||
char** scene_collections = obs_frontend_get_scene_collections();
|
||||
obs_data_array_t* list = string_list_to_array(scene_collections, "sc-name");
|
||||
|
||||
bfree(scene_collections);
|
||||
return list;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetProfiles() {
|
||||
char** profiles = obs_frontend_get_profiles();
|
||||
obs_data_array_t* list = string_list_to_array(profiles, "profile-name");
|
||||
|
||||
bfree(profiles);
|
||||
return list;
|
||||
}
|
||||
|
||||
QSpinBox* Utils::GetTransitionDurationControl() {
|
||||
QMainWindow* window = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return window->findChild<QSpinBox*>("transitionDuration");
|
||||
}
|
||||
|
||||
int Utils::GetTransitionDuration() {
|
||||
QSpinBox* control = GetTransitionDurationControl();
|
||||
if (control)
|
||||
return control->value();
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Utils::SetTransitionDuration(int ms) {
|
||||
QSpinBox* control = GetTransitionDurationControl();
|
||||
if (control && ms >= 0)
|
||||
control->setValue(ms);
|
||||
}
|
||||
|
||||
bool Utils::SetTransitionByName(const char* transition_name) {
|
||||
obs_source_t* transition = GetTransitionFromName(transition_name);
|
||||
|
||||
if (transition) {
|
||||
obs_frontend_set_current_transition(transition);
|
||||
obs_source_release(transition);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QPushButton* Utils::GetPreviewModeButtonControl() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChild<QPushButton*>("modeSwitch");
|
||||
}
|
||||
|
||||
QListWidget* Utils::GetSceneListControl() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChild<QListWidget*>("scenes");
|
||||
}
|
||||
|
||||
obs_scene_t* Utils::SceneListItemToScene(QListWidgetItem* item) {
|
||||
if (!item)
|
||||
return nullptr;
|
||||
|
||||
QVariant item_data = item->data(static_cast<int>(Qt::UserRole));
|
||||
return item_data.value<OBSScene>();
|
||||
}
|
||||
|
||||
QLayout* Utils::GetPreviewLayout() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChild<QLayout*>("previewLayout");
|
||||
}
|
||||
|
||||
bool Utils::IsPreviewModeActive() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
|
||||
// Clue 1 : "Studio Mode" button is toggled on
|
||||
bool buttonToggledOn = GetPreviewModeButtonControl()->isChecked();
|
||||
|
||||
// Clue 2 : Preview layout has more than one item
|
||||
int previewChildCount = GetPreviewLayout()->count();
|
||||
blog(LOG_INFO, "preview layout children count : %d", previewChildCount);
|
||||
|
||||
return buttonToggledOn || (previewChildCount >= 2);
|
||||
}
|
||||
|
||||
void Utils::EnablePreviewMode() {
|
||||
if (!IsPreviewModeActive())
|
||||
GetPreviewModeButtonControl()->click();
|
||||
}
|
||||
|
||||
void Utils::DisablePreviewMode() {
|
||||
if (IsPreviewModeActive())
|
||||
GetPreviewModeButtonControl()->click();
|
||||
}
|
||||
|
||||
void Utils::TogglePreviewMode() {
|
||||
GetPreviewModeButtonControl()->click();
|
||||
}
|
||||
|
||||
obs_scene_t* Utils::GetPreviewScene() {
|
||||
if (IsPreviewModeActive()) {
|
||||
QListWidget* sceneList = GetSceneListControl();
|
||||
QList<QListWidgetItem*> selected = sceneList->selectedItems();
|
||||
|
||||
// Qt::UserRole == QtUserRole::OBSRef
|
||||
obs_scene_t* scene = Utils::SceneListItemToScene(selected.first());
|
||||
|
||||
obs_scene_addref(scene);
|
||||
return scene;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Utils::SetPreviewScene(const char* name) {
|
||||
if (IsPreviewModeActive()) {
|
||||
QListWidget* sceneList = GetSceneListControl();
|
||||
QList<QListWidgetItem*> matchingItems =
|
||||
sceneList->findItems(name, Qt::MatchExactly);
|
||||
|
||||
if (matchingItems.count() > 0) {
|
||||
sceneList->setCurrentItem(matchingItems.first());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Utils::TransitionToProgram() {
|
||||
if (!IsPreviewModeActive())
|
||||
return;
|
||||
|
||||
// WARNING : if the layout created in OBS' CreateProgramOptions() changes
|
||||
// then this won't work as expected
|
||||
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
|
||||
// The program options widget is the second item in the left-to-right layout
|
||||
QWidget* programOptions = GetPreviewLayout()->itemAt(1)->widget();
|
||||
|
||||
// The "Transition" button lies in the mainButtonLayout
|
||||
// which is the first itemin the program options' layout
|
||||
QLayout* mainButtonLayout = programOptions->layout()->itemAt(1)->layout();
|
||||
QWidget* transitionBtnWidget = mainButtonLayout->itemAt(0)->widget();
|
||||
|
||||
// Try to cast that widget into a button
|
||||
QPushButton* transitionBtn = qobject_cast<QPushButton*>(transitionBtnWidget);
|
||||
|
||||
// Perform a click on that button
|
||||
transitionBtn->click();
|
||||
}
|
||||
|
||||
const char* Utils::OBSVersionString() {
|
||||
uint32_t version = obs_get_version();
|
||||
|
||||
uint8_t major, minor, patch;
|
||||
major = (version >> 24) & 0xFF;
|
||||
minor = (version >> 16) & 0xFF;
|
||||
patch = version & 0xFF;
|
||||
|
||||
char* result = (char*)bmalloc(sizeof(char) * 12);
|
||||
sprintf(result, "%d.%d.%d", major, minor, patch);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QSystemTrayIcon* Utils::GetTrayIcon() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChildren<QSystemTrayIcon*>().first();
|
||||
}
|
||||
|
||||
void Utils::SysTrayNotify(QString &text,
|
||||
QSystemTrayIcon::MessageIcon icon, QString title) {
|
||||
if (!QSystemTrayIcon::supportsMessages())
|
||||
return;
|
||||
|
||||
QSystemTrayIcon* trayIcon = GetTrayIcon();
|
||||
if (trayIcon)
|
||||
trayIcon->showMessage(title, text, icon);
|
||||
}
|
||||
|
||||
QString Utils::FormatIPAddress(QHostAddress &addr) {
|
||||
QRegExp v4regex("(::ffff:)(((\\d).){3})", Qt::CaseInsensitive);
|
||||
QString addrString = addr.toString();
|
||||
if (addrString.contains(v4regex)) {
|
||||
addrString = QHostAddress(addr.toIPv4Address()).toString();
|
||||
}
|
||||
return addrString;
|
||||
}
|
||||
|
||||
const char* Utils::GetRecordingFolder() {
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
const char* outputMode = config_get_string(profile, "Output", "Mode");
|
||||
|
||||
if (strcmp(outputMode, "Advanced") == 0) {
|
||||
// Advanced mode
|
||||
return config_get_string(profile, "AdvOut", "RecFilePath");
|
||||
} else {
|
||||
// Simple mode
|
||||
return config_get_string(profile, "SimpleOutput", "FilePath");
|
||||
}
|
||||
}
|
||||
|
||||
bool Utils::SetRecordingFolder(const char* path) {
|
||||
if (!QDir(path).exists())
|
||||
return false;
|
||||
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
const char* outputMode = config_get_string(profile, "Output", "Mode");
|
||||
|
||||
if (strcmp(outputMode, "Advanced") == 0) {
|
||||
config_set_string(profile, "AdvOut", "RecFilePath", path);
|
||||
} else {
|
||||
config_set_string(profile, "SimpleOutput", "FilePath", path);
|
||||
}
|
||||
|
||||
config_save(profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
QString* Utils::ParseDataToQueryString(obs_data_t* data) {
|
||||
QString* query = nullptr;
|
||||
if (data) {
|
||||
obs_data_item_t* item = obs_data_first(data);
|
||||
if (item) {
|
||||
query = new QString();
|
||||
bool isFirst = true;
|
||||
do {
|
||||
if (!obs_data_item_has_user_value(item))
|
||||
continue;
|
||||
|
||||
if (!isFirst)
|
||||
query->append('&');
|
||||
else
|
||||
isFirst = false;
|
||||
|
||||
const char* attrName = obs_data_item_get_name(item);
|
||||
query->append(attrName).append("=");
|
||||
|
||||
switch (obs_data_item_gettype(item)) {
|
||||
case OBS_DATA_BOOLEAN:
|
||||
query->append(obs_data_item_get_bool(item)?"true":"false");
|
||||
break;
|
||||
case OBS_DATA_NUMBER:
|
||||
switch (obs_data_item_numtype(item))
|
||||
{
|
||||
case OBS_DATA_NUM_DOUBLE:
|
||||
query->append(
|
||||
QString::number(obs_data_item_get_double(item)));
|
||||
break;
|
||||
case OBS_DATA_NUM_INT:
|
||||
query->append(
|
||||
QString::number(obs_data_item_get_int(item)));
|
||||
break;
|
||||
case OBS_DATA_NUM_INVALID:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case OBS_DATA_STRING:
|
||||
query->append(QUrl::toPercentEncoding(
|
||||
QString(obs_data_item_get_string(item))));
|
||||
break;
|
||||
default:
|
||||
//other types are not supported
|
||||
break;
|
||||
}
|
||||
} while (obs_data_item_next(&item));
|
||||
}
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
obs_hotkey_t* Utils::FindHotkeyByName(const char* name) {
|
||||
struct current_search {
|
||||
const char* query;
|
||||
obs_hotkey_t* result;
|
||||
};
|
||||
|
||||
current_search search;
|
||||
search.query = name;
|
||||
search.result = nullptr;
|
||||
|
||||
obs_enum_hotkeys([](void* data, obs_hotkey_id id, obs_hotkey_t* hotkey) {
|
||||
current_search* search = static_cast<current_search*>(data);
|
||||
|
||||
const char* hk_name = obs_hotkey_get_name(hotkey);
|
||||
if (strcmp(hk_name, search->query) == 0) {
|
||||
search->result = hotkey;
|
||||
blog(LOG_INFO, "Utils::FindHotkeyByName: found %s", hk_name);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, &search);
|
||||
|
||||
return search.result;
|
||||
}
|
||||
|
||||
bool Utils::ReplayBufferEnabled() {
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
const char* outputMode = config_get_string(profile, "Output", "Mode");
|
||||
|
||||
if (strcmp(outputMode, "Simple") == 0) {
|
||||
return config_get_bool(profile, "SimpleOutput", "RecRB");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Utils::RPHotkeySet() {
|
||||
obs_output_t* rp_output = obs_frontend_get_replay_buffer_output();
|
||||
|
||||
obs_data_t *hotkeys = obs_hotkeys_save_output(rp_output);
|
||||
obs_data_array_t *bindings = obs_data_get_array(hotkeys,
|
||||
"ReplayBuffer.Save");
|
||||
|
||||
size_t count = obs_data_array_count(bindings);
|
||||
|
||||
obs_data_array_release(bindings);
|
||||
obs_data_release(hotkeys);
|
||||
obs_output_release(rp_output);
|
||||
|
||||
return (count > 0);
|
||||
}
|
86
Utils.h
86
Utils.h
@ -1,86 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <QPushButton>
|
||||
#include <QLayout>
|
||||
#include <QListWidget>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <obs-module.h>
|
||||
#include <util/config-file.h>
|
||||
|
||||
class Utils {
|
||||
public:
|
||||
static obs_data_array_t* GetSceneItems(obs_source_t* source);
|
||||
static obs_data_t* GetSceneItemData(obs_scene_item* item);
|
||||
static obs_sceneitem_t* GetSceneItemFromName(
|
||||
obs_source_t* source, const char* name);
|
||||
static obs_source_t* GetTransitionFromName(const char* search_name);
|
||||
static obs_source_t* GetSceneFromNameOrCurrent(const char* scene_name);
|
||||
|
||||
static obs_data_array_t* GetScenes();
|
||||
static obs_data_t* GetSceneData(obs_source* source);
|
||||
|
||||
static obs_data_array_t* GetSceneCollections();
|
||||
static obs_data_array_t* GetProfiles();
|
||||
|
||||
static QSpinBox* GetTransitionDurationControl();
|
||||
static int GetTransitionDuration();
|
||||
static void SetTransitionDuration(int ms);
|
||||
|
||||
static bool SetTransitionByName(const char* transition_name);
|
||||
|
||||
static QPushButton* GetPreviewModeButtonControl();
|
||||
static QLayout* GetPreviewLayout();
|
||||
static QListWidget* GetSceneListControl();
|
||||
static obs_scene_t* SceneListItemToScene(QListWidgetItem* item);
|
||||
|
||||
static bool IsPreviewModeActive();
|
||||
static void EnablePreviewMode();
|
||||
static void DisablePreviewMode();
|
||||
static void TogglePreviewMode();
|
||||
|
||||
static obs_scene_t* GetPreviewScene();
|
||||
static bool SetPreviewScene(const char* name);
|
||||
static void TransitionToProgram();
|
||||
|
||||
static const char* OBSVersionString();
|
||||
|
||||
static QSystemTrayIcon* GetTrayIcon();
|
||||
static void SysTrayNotify(
|
||||
QString &text,
|
||||
QSystemTrayIcon::MessageIcon n,
|
||||
QString title = QString("obs-websocket"));
|
||||
|
||||
static QString FormatIPAddress(QHostAddress &addr);
|
||||
static const char* GetRecordingFolder();
|
||||
static bool SetRecordingFolder(const char* path);
|
||||
|
||||
static QString* ParseDataToQueryString(obs_data_t * data);
|
||||
static obs_hotkey_t* FindHotkeyByName(const char* name);
|
||||
static bool ReplayBufferEnabled();
|
||||
static bool RPHotkeySet();
|
||||
};
|
||||
|
||||
#endif // UTILS_H
|
887
WSEvents.cpp
887
WSEvents.cpp
@ -1,887 +0,0 @@
|
||||
/**
|
||||
* obs-websocket
|
||||
* Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
* Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <util/platform.h>
|
||||
|
||||
#include <QTimer>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "Config.h"
|
||||
#include "Utils.h"
|
||||
#include "WSEvents.h"
|
||||
|
||||
#include "obs-websocket.h"
|
||||
|
||||
bool transition_is_cut(obs_source_t* transition) {
|
||||
if (!transition)
|
||||
return false;
|
||||
|
||||
if (obs_source_get_type(transition) == OBS_SOURCE_TYPE_TRANSITION
|
||||
&& strcmp(obs_source_get_id(transition), "cut_transition") == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* ns_to_timestamp(uint64_t ns) {
|
||||
uint64_t ms = ns / (1000 * 1000);
|
||||
uint64_t secs = ms / 1000;
|
||||
uint64_t minutes = secs / 60;
|
||||
|
||||
uint64_t hours_part = minutes / 60;
|
||||
uint64_t minutes_part = minutes % 60;
|
||||
uint64_t secs_part = secs % 60;
|
||||
uint64_t ms_part = ms % 1000;
|
||||
|
||||
char* ts = (char*)bmalloc(64);
|
||||
sprintf(ts, "%02d:%02d:%02d.%03d",
|
||||
hours_part, minutes_part, secs_part, ms_part);
|
||||
|
||||
return ts;
|
||||
}
|
||||
|
||||
WSEvents* WSEvents::Instance = nullptr;
|
||||
|
||||
WSEvents::WSEvents(WSServer* srv) {
|
||||
_srv = srv;
|
||||
obs_frontend_add_event_callback(WSEvents::FrontendEventHandler, this);
|
||||
|
||||
QSpinBox* duration_control = Utils::GetTransitionDurationControl();
|
||||
connect(duration_control, SIGNAL(valueChanged(int)),
|
||||
this, SLOT(TransitionDurationChanged(int)));
|
||||
|
||||
QTimer* statusTimer = new QTimer();
|
||||
connect(statusTimer, SIGNAL(timeout()),
|
||||
this, SLOT(StreamStatus()));
|
||||
statusTimer->start(2000); // equal to frontend's constant BITRATE_UPDATE_SECONDS
|
||||
|
||||
QListWidget* sceneList = Utils::GetSceneListControl();
|
||||
connect(sceneList, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)),
|
||||
this, SLOT(SelectedSceneChanged(QListWidgetItem*, QListWidgetItem*)));
|
||||
|
||||
QPushButton* modeSwitch = Utils::GetPreviewModeButtonControl();
|
||||
connect(modeSwitch, SIGNAL(clicked(bool)), this, SLOT(ModeSwitchClicked(bool)));
|
||||
|
||||
transition_handler = nullptr;
|
||||
scene_handler = nullptr;
|
||||
|
||||
QTimer::singleShot(1000, this, SLOT(deferredInitOperations()));
|
||||
|
||||
_streaming_active = false;
|
||||
_recording_active = false;
|
||||
|
||||
_stream_starttime = 0;
|
||||
_rec_starttime = 0;
|
||||
}
|
||||
|
||||
WSEvents::~WSEvents() {
|
||||
obs_frontend_remove_event_callback(WSEvents::FrontendEventHandler, this);
|
||||
}
|
||||
|
||||
void WSEvents::deferredInitOperations() {
|
||||
obs_source_t* transition = obs_frontend_get_current_transition();
|
||||
connectTransitionSignals(transition);
|
||||
obs_source_release(transition);
|
||||
|
||||
obs_source_t* scene = obs_frontend_get_current_scene();
|
||||
connectSceneSignals(scene);
|
||||
obs_source_release(scene);
|
||||
}
|
||||
|
||||
void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private_data) {
|
||||
WSEvents* owner = static_cast<WSEvents*>(private_data);
|
||||
|
||||
if (!owner->_srv)
|
||||
return;
|
||||
|
||||
// TODO : implement SourceOrderChanged and RepopulateSources
|
||||
|
||||
if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED) {
|
||||
owner->OnSceneChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED) {
|
||||
owner->OnSceneListChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED) {
|
||||
owner->OnSceneCollectionChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED) {
|
||||
owner->OnSceneCollectionListChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_TRANSITION_CHANGED) {
|
||||
owner->OnTransitionChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED) {
|
||||
owner->OnTransitionListChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_PROFILE_CHANGED) {
|
||||
owner->OnProfileChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED) {
|
||||
owner->OnProfileListChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTING) {
|
||||
owner->OnStreamStarting();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTED) {
|
||||
owner->_streaming_active = true;
|
||||
owner->OnStreamStarted();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPING) {
|
||||
owner->OnStreamStopping();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPED) {
|
||||
owner->_streaming_active = false;
|
||||
owner->OnStreamStopped();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING) {
|
||||
owner->OnRecordingStarting();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED) {
|
||||
owner->_recording_active = true;
|
||||
owner->OnRecordingStarted();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPING) {
|
||||
owner->OnRecordingStopping();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPED) {
|
||||
owner->_recording_active = false;
|
||||
owner->OnRecordingStopped();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING) {
|
||||
owner->OnReplayStarting();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED) {
|
||||
owner->OnReplayStarted();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING) {
|
||||
owner->OnReplayStopping();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED) {
|
||||
owner->OnReplayStopped();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_EXIT) {
|
||||
owner->OnExit();
|
||||
}
|
||||
}
|
||||
|
||||
void WSEvents::broadcastUpdate(const char* updateType,
|
||||
obs_data_t* additionalFields = NULL) {
|
||||
obs_data_t* update = obs_data_create();
|
||||
obs_data_set_string(update, "update-type", updateType);
|
||||
|
||||
const char* ts = nullptr;
|
||||
if (_streaming_active) {
|
||||
ts = ns_to_timestamp(os_gettime_ns() - _stream_starttime);
|
||||
obs_data_set_string(update, "stream-timecode", ts);
|
||||
bfree((void*)ts);
|
||||
}
|
||||
|
||||
if (_recording_active) {
|
||||
ts = ns_to_timestamp(os_gettime_ns() - _rec_starttime);
|
||||
obs_data_set_string(update, "rec-timecode", ts);
|
||||
bfree((void*)ts);
|
||||
}
|
||||
|
||||
if (additionalFields != NULL)
|
||||
obs_data_apply(update, additionalFields);
|
||||
|
||||
const char *json = obs_data_get_json(update);
|
||||
_srv->broadcast(json);
|
||||
if (Config::Current()->DebugEnabled)
|
||||
blog(LOG_DEBUG, "Update << '%s'", json);
|
||||
|
||||
obs_data_release(update);
|
||||
}
|
||||
|
||||
void WSEvents::connectTransitionSignals(obs_source_t* transition) {
|
||||
if (transition_handler) {
|
||||
signal_handler_disconnect(transition_handler,
|
||||
"transition_start", OnTransitionBegin, this);
|
||||
}
|
||||
|
||||
if (!transition_is_cut(transition)) {
|
||||
transition_handler = obs_source_get_signal_handler(transition);
|
||||
signal_handler_connect(transition_handler,
|
||||
"transition_start", OnTransitionBegin, this);
|
||||
} else {
|
||||
transition_handler = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void WSEvents::connectSceneSignals(obs_source_t* scene) {
|
||||
if (scene_handler) {
|
||||
signal_handler_disconnect(scene_handler,
|
||||
"reorder", OnSceneReordered, this);
|
||||
signal_handler_disconnect(scene_handler,
|
||||
"item_add", OnSceneItemAdd, this);
|
||||
signal_handler_disconnect(scene_handler,
|
||||
"item_remove", OnSceneItemDelete, this);
|
||||
signal_handler_disconnect(scene_handler,
|
||||
"item_visible", OnSceneItemVisibilityChanged, this);
|
||||
}
|
||||
|
||||
// TODO : connect to all scenes, not just the current one.
|
||||
scene_handler = obs_source_get_signal_handler(scene);
|
||||
signal_handler_connect(scene_handler,
|
||||
"reorder", OnSceneReordered, this);
|
||||
signal_handler_connect(scene_handler,
|
||||
"item_add", OnSceneItemAdd, this);
|
||||
signal_handler_connect(scene_handler,
|
||||
"item_remove", OnSceneItemDelete, this);
|
||||
signal_handler_connect(scene_handler,
|
||||
"item_visible", OnSceneItemVisibilityChanged, this);
|
||||
}
|
||||
|
||||
uint64_t WSEvents::GetStreamingTime() {
|
||||
if (_streaming_active)
|
||||
return (os_gettime_ns() - _stream_starttime);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* WSEvents::GetStreamingTimecode() {
|
||||
return ns_to_timestamp(GetStreamingTime());
|
||||
}
|
||||
|
||||
uint64_t WSEvents::GetRecordingTime() {
|
||||
if (_recording_active)
|
||||
return (os_gettime_ns() - _rec_starttime);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* WSEvents::GetRecordingTimecode() {
|
||||
return ns_to_timestamp(GetRecordingTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates a scene change.
|
||||
*
|
||||
* @return {String} `scene-name` The new scene.
|
||||
* @return {Array} `sources` List of sources in the new scene.
|
||||
*
|
||||
* @api events
|
||||
* @name SwitchScenes
|
||||
* @category scenes
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnSceneChange() {
|
||||
obs_data_t* data = obs_data_create();
|
||||
|
||||
obs_source_t* current_scene = obs_frontend_get_current_scene();
|
||||
obs_data_array_t* scene_items = Utils::GetSceneItems(current_scene);
|
||||
connectSceneSignals(current_scene);
|
||||
|
||||
obs_data_set_string(data, "scene-name", obs_source_get_name(current_scene));
|
||||
obs_data_set_array(data, "sources", scene_items);
|
||||
|
||||
broadcastUpdate("SwitchScenes", data);
|
||||
|
||||
obs_data_array_release(scene_items);
|
||||
obs_source_release(current_scene);
|
||||
obs_data_release(data);
|
||||
|
||||
// Dirty fix : OBS blocks signals when swapping scenes in Studio Mode
|
||||
// after transition end, so SelectedSceneChanged is never called...
|
||||
if (Utils::IsPreviewModeActive()) {
|
||||
QListWidget* list = Utils::GetSceneListControl();
|
||||
SelectedSceneChanged(list->currentItem(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The scene list has been modified.
|
||||
* Scenes have been added, removed, or renamed.
|
||||
*
|
||||
* @api events
|
||||
* @name ScenesChanged
|
||||
* @category scenes
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnSceneListChange() {
|
||||
broadcastUpdate("ScenesChanged");
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when switching to another scene collection or when renaming the current scene collection.
|
||||
*
|
||||
* @api events
|
||||
* @name SceneCollectionChanged
|
||||
* @category scenes
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSEvents::OnSceneCollectionChange() {
|
||||
broadcastUpdate("SceneCollectionChanged");
|
||||
|
||||
scene_handler = nullptr;
|
||||
transition_handler = nullptr;
|
||||
|
||||
OnTransitionListChange();
|
||||
OnTransitionChange();
|
||||
|
||||
OnSceneListChange();
|
||||
OnSceneChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when a scene collection is created, added, renamed, or removed.
|
||||
*
|
||||
* @api events
|
||||
* @name SceneCollectionListChanged
|
||||
* @category scenes
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSEvents::OnSceneCollectionListChange() {
|
||||
broadcastUpdate("SceneCollectionListChanged");
|
||||
}
|
||||
|
||||
/**
|
||||
* The active transition has been changed.
|
||||
*
|
||||
* @return {String} `transition-name` The name of the new active transition.
|
||||
*
|
||||
* @api events
|
||||
* @name SwitchTransition
|
||||
* @category transitions
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSEvents::OnTransitionChange() {
|
||||
obs_source_t* current_transition = obs_frontend_get_current_transition();
|
||||
connectTransitionSignals(current_transition);
|
||||
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_string(data, "transition-name",
|
||||
obs_source_get_name(current_transition));
|
||||
|
||||
broadcastUpdate("SwitchTransition", data);
|
||||
|
||||
obs_data_release(data);
|
||||
obs_source_release(current_transition);
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of available transitions has been modified.
|
||||
* Transitions have been added, removed, or renamed.
|
||||
*
|
||||
* @api events
|
||||
* @name TransitionListChanged
|
||||
* @category transitions
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSEvents::OnTransitionListChange() {
|
||||
broadcastUpdate("TransitionListChanged");
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when switching to another profile or when renaming the current profile.
|
||||
*
|
||||
* @api events
|
||||
* @name ProfileChanged
|
||||
* @category profiles
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSEvents::OnProfileChange() {
|
||||
broadcastUpdate("ProfileChanged");
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when a profile is created, added, renamed, or removed.
|
||||
*
|
||||
* @api events
|
||||
* @name ProfileListChanged
|
||||
* @category profiles
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSEvents::OnProfileListChange() {
|
||||
broadcastUpdate("ProfileListChanged");
|
||||
}
|
||||
|
||||
/**
|
||||
* A request to start streaming has been issued.
|
||||
*
|
||||
* @return {boolean} `preview-only` Always false (retrocompatibility).
|
||||
*
|
||||
* @api events
|
||||
* @name StreamStarting
|
||||
* @category streaming
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnStreamStarting() {
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_bool(data, "preview-only", false);
|
||||
|
||||
broadcastUpdate("StreamStarting", data);
|
||||
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streaming started successfully.
|
||||
*
|
||||
* @api events
|
||||
* @name StreamStarted
|
||||
* @category streaming
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnStreamStarted() {
|
||||
_stream_starttime = os_gettime_ns();
|
||||
_lastBytesSent = 0;
|
||||
broadcastUpdate("StreamStarted");
|
||||
}
|
||||
|
||||
/**
|
||||
* A request to stop streaming has been issued.
|
||||
*
|
||||
* @return {boolean} `preview-only` Always false (retrocompatibility).
|
||||
*
|
||||
* @api events
|
||||
* @name StreamStopping
|
||||
* @category streaming
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnStreamStopping() {
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_bool(data, "preview-only", false);
|
||||
|
||||
broadcastUpdate("StreamStopping", data);
|
||||
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streaming stopped successfully.
|
||||
*
|
||||
* @api events
|
||||
* @name StreamStopped
|
||||
* @category streaming
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnStreamStopped() {
|
||||
_stream_starttime = 0;
|
||||
broadcastUpdate("StreamStopped");
|
||||
}
|
||||
|
||||
/**
|
||||
* A request to start recording has been issued.
|
||||
*
|
||||
* @api events
|
||||
* @name RecordingStarting
|
||||
* @category recording
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnRecordingStarting() {
|
||||
broadcastUpdate("RecordingStarting");
|
||||
}
|
||||
|
||||
/**
|
||||
* Recording started successfully.
|
||||
*
|
||||
* @api events
|
||||
* @name RecordingStarted
|
||||
* @category recording
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnRecordingStarted() {
|
||||
_rec_starttime = os_gettime_ns();
|
||||
broadcastUpdate("RecordingStarted");
|
||||
}
|
||||
|
||||
/**
|
||||
* A request to stop recording has been issued.
|
||||
*
|
||||
* @api events
|
||||
* @name RecordingStopping
|
||||
* @category recording
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnRecordingStopping() {
|
||||
broadcastUpdate("RecordingStopping");
|
||||
}
|
||||
|
||||
/**
|
||||
* Recording stopped successfully.
|
||||
*
|
||||
* @api events
|
||||
* @name RecordingStopped
|
||||
* @category recording
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnRecordingStopped() {
|
||||
_rec_starttime = 0;
|
||||
broadcastUpdate("RecordingStopped");
|
||||
}
|
||||
|
||||
/**
|
||||
* A request to start the replay buffer has been issued.
|
||||
*
|
||||
* @api events
|
||||
* @name ReplayStarting
|
||||
* @category replay buffer
|
||||
* @since 4.2.0
|
||||
*/
|
||||
void WSEvents::OnReplayStarting() {
|
||||
broadcastUpdate("ReplayStarting");
|
||||
}
|
||||
|
||||
/**
|
||||
* Replay Buffer started successfully
|
||||
*
|
||||
* @api events
|
||||
* @name ReplayStarted
|
||||
* @category replay buffer
|
||||
* @since 4.2.0
|
||||
*/
|
||||
void WSEvents::OnReplayStarted() {
|
||||
broadcastUpdate("ReplayStarted");
|
||||
}
|
||||
|
||||
/**
|
||||
* A request to start the replay buffer has been issued.
|
||||
*
|
||||
* @api events
|
||||
* @name ReplayStopping
|
||||
* @category replay buffer
|
||||
* @since 4.2.0
|
||||
*/
|
||||
void WSEvents::OnReplayStopping() {
|
||||
broadcastUpdate("ReplayStopping");
|
||||
}
|
||||
|
||||
/**
|
||||
* Replay Buffer stopped successfully
|
||||
*
|
||||
* @api events
|
||||
* @name ReplayStopped
|
||||
* @category replay buffer
|
||||
* @since 4.2.0
|
||||
*/
|
||||
void WSEvents::OnReplayStopped() {
|
||||
broadcastUpdate("ReplayStopped");
|
||||
}
|
||||
|
||||
/**
|
||||
* OBS is exiting.
|
||||
*
|
||||
* @api events
|
||||
* @name Exiting
|
||||
* @category other
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnExit() {
|
||||
broadcastUpdate("Exiting");
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit every 2 seconds.
|
||||
*
|
||||
* @return {boolean} `streaming` Current streaming state.
|
||||
* @return {boolean} `recording` Current recording state.
|
||||
* @return {boolean} `preview-only` Always false (retrocompatibility).
|
||||
* @return {int} `bytes-per-sec` Amount of data per second (in bytes) transmitted by the stream encoder.
|
||||
* @return {int} `kbits-per-sec` Amount of data per second (in kilobits) transmitted by the stream encoder.
|
||||
* @return {double} `strain` Percentage of dropped frames.
|
||||
* @return {int} `total-stream-time` Total time (in seconds) since the stream started.
|
||||
* @return {int} `num-total-frames` Total number of frames transmitted since the stream started.
|
||||
* @return {int} `num-dropped-frames` Number of frames dropped by the encoder since the stream started.
|
||||
* @return {double} `fps` Current framerate.
|
||||
*
|
||||
* @api events
|
||||
* @name StreamStatus
|
||||
* @category streaming
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::StreamStatus() {
|
||||
bool streaming_active = obs_frontend_streaming_active();
|
||||
bool recording_active = obs_frontend_recording_active();
|
||||
|
||||
obs_output_t* stream_output = obs_frontend_get_streaming_output();
|
||||
|
||||
if (!stream_output || !streaming_active) {
|
||||
if (stream_output) {
|
||||
obs_output_release(stream_output);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t bytes_sent = obs_output_get_total_bytes(stream_output);
|
||||
uint64_t bytes_sent_time = os_gettime_ns();
|
||||
|
||||
if (bytes_sent < _lastBytesSent)
|
||||
bytes_sent = 0;
|
||||
|
||||
if (bytes_sent == 0)
|
||||
_lastBytesSent = 0;
|
||||
|
||||
uint64_t bytes_between = bytes_sent - _lastBytesSent;
|
||||
double time_passed =
|
||||
double(bytes_sent_time - _lastBytesSentTime) / 1000000000.0;
|
||||
|
||||
uint64_t bytes_per_sec = bytes_between / time_passed;
|
||||
|
||||
_lastBytesSent = bytes_sent;
|
||||
_lastBytesSentTime = bytes_sent_time;
|
||||
|
||||
uint64_t totalStreamTime =
|
||||
(os_gettime_ns() - _stream_starttime) / 1000000000;
|
||||
|
||||
int total_frames = obs_output_get_total_frames(stream_output);
|
||||
int dropped_frames = obs_output_get_frames_dropped(stream_output);
|
||||
|
||||
float strain = obs_output_get_congestion(stream_output);
|
||||
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_bool(data, "streaming", streaming_active);
|
||||
obs_data_set_bool(data, "recording", recording_active);
|
||||
obs_data_set_int(data, "bytes-per-sec", bytes_per_sec);
|
||||
obs_data_set_int(data, "kbits-per-sec", (bytes_per_sec * 8) / 1024);
|
||||
obs_data_set_int(data, "total-stream-time", totalStreamTime);
|
||||
obs_data_set_int(data, "num-total-frames", total_frames);
|
||||
obs_data_set_int(data, "num-dropped-frames", dropped_frames);
|
||||
obs_data_set_double(data, "fps", obs_get_active_fps());
|
||||
obs_data_set_double(data, "strain", strain);
|
||||
obs_data_set_bool(data, "preview-only", false); // Retrocompat with OBSRemote
|
||||
|
||||
broadcastUpdate("StreamStatus", data);
|
||||
|
||||
obs_data_release(data);
|
||||
obs_output_release(stream_output);
|
||||
}
|
||||
|
||||
/**
|
||||
* The active transition duration has been changed.
|
||||
*
|
||||
* @return {int} `new-duration` New transition duration.
|
||||
*
|
||||
* @api events
|
||||
* @name TransitionDurationChanged
|
||||
* @category transitions
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSEvents::TransitionDurationChanged(int ms) {
|
||||
obs_data_t* fields = obs_data_create();
|
||||
obs_data_set_int(fields, "new-duration", ms);
|
||||
|
||||
broadcastUpdate("TransitionDurationChanged", fields);
|
||||
obs_data_release(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* A transition (other than "cut") has begun.
|
||||
*
|
||||
* @return {String} `name` Transition name.
|
||||
* @return {int} `duration` Transition duration (in milliseconds).
|
||||
*
|
||||
* @api events
|
||||
* @name TransitionBegin
|
||||
* @category transitions
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSEvents::OnTransitionBegin(void* param, calldata_t* data) {
|
||||
UNUSED_PARAMETER(data);
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
|
||||
obs_source_t* current_transition = obs_frontend_get_current_transition();
|
||||
const char* name = obs_source_get_name(current_transition);
|
||||
int duration = Utils::GetTransitionDuration();
|
||||
|
||||
obs_data_t* fields = obs_data_create();
|
||||
obs_data_set_string(fields, "name", name);
|
||||
obs_data_set_int(fields, "duration", duration);
|
||||
|
||||
instance->broadcastUpdate("TransitionBegin", fields);
|
||||
obs_data_release(fields);
|
||||
obs_source_release(current_transition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scene items have been reordered.
|
||||
*
|
||||
* @return {String} `scene-name` Name of the scene where items have been reordered.
|
||||
*
|
||||
* @api events
|
||||
* @name SourceOrderChanged
|
||||
* @category sources
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSEvents::OnSceneReordered(void* param, calldata_t* data) {
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
obs_data_t* fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name",
|
||||
obs_source_get_name(obs_scene_get_source(scene)));
|
||||
|
||||
instance->broadcastUpdate("SourceOrderChanged", fields);
|
||||
obs_data_release(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* An item has been added to the current scene.
|
||||
*
|
||||
* @return {String} `scene-name` Name of the scene.
|
||||
* @return {String} `item-name` Name of the item added to the scene.
|
||||
*
|
||||
* @api events
|
||||
* @name SceneItemAdded
|
||||
* @category sources
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSEvents::OnSceneItemAdd(void* param, calldata_t* data) {
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
obs_sceneitem_t* scene_item = nullptr;
|
||||
calldata_get_ptr(data, "item", &scene_item);
|
||||
|
||||
const char* scene_name =
|
||||
obs_source_get_name(obs_scene_get_source(scene));
|
||||
const char* sceneitem_name =
|
||||
obs_source_get_name(obs_sceneitem_get_source(scene_item));
|
||||
|
||||
obs_data_t* fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name", scene_name);
|
||||
obs_data_set_string(fields, "item-name", sceneitem_name);
|
||||
|
||||
instance->broadcastUpdate("SceneItemAdded", fields);
|
||||
obs_data_release(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* An item has been removed from the current scene.
|
||||
*
|
||||
* @return {String} `scene-name` Name of the scene.
|
||||
* @return {String} `item-name` Name of the item removed from the scene.
|
||||
*
|
||||
* @api events
|
||||
* @name SceneItemRemoved
|
||||
* @category sources
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSEvents::OnSceneItemDelete(void* param, calldata_t* data) {
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
obs_sceneitem_t* scene_item = nullptr;
|
||||
calldata_get_ptr(data, "item", &scene_item);
|
||||
|
||||
const char* scene_name =
|
||||
obs_source_get_name(obs_scene_get_source(scene));
|
||||
const char* sceneitem_name =
|
||||
obs_source_get_name(obs_sceneitem_get_source(scene_item));
|
||||
|
||||
obs_data_t* fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name", scene_name);
|
||||
obs_data_set_string(fields, "item-name", sceneitem_name);
|
||||
|
||||
instance->broadcastUpdate("SceneItemRemoved", fields);
|
||||
obs_data_release(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* An item's visibility has been toggled.
|
||||
*
|
||||
* @return {String} `scene-name` Name of the scene.
|
||||
* @return {String} `item-name` Name of the item in the scene.
|
||||
* @return {boolean} `item-visible` New visibility state of the item.
|
||||
*
|
||||
* @api events
|
||||
* @name SceneItemVisibilityChanged
|
||||
* @category sources
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) {
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
obs_sceneitem_t* scene_item = nullptr;
|
||||
calldata_get_ptr(data, "item", &scene_item);
|
||||
|
||||
bool visible = false;
|
||||
calldata_get_bool(data, "visible", &visible);
|
||||
|
||||
const char* scene_name =
|
||||
obs_source_get_name(obs_scene_get_source(scene));
|
||||
const char* sceneitem_name =
|
||||
obs_source_get_name(obs_sceneitem_get_source(scene_item));
|
||||
|
||||
obs_data_t* fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name", scene_name);
|
||||
obs_data_set_string(fields, "item-name", sceneitem_name);
|
||||
obs_data_set_bool(fields, "item-visible", visible);
|
||||
|
||||
instance->broadcastUpdate("SceneItemVisibilityChanged", fields);
|
||||
obs_data_release(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* The selected preview scene has changed (only available in Studio Mode).
|
||||
*
|
||||
* @return {String} `scene-name` Name of the scene being previewed.
|
||||
* @return {Source|Array} `sources` List of sources composing the scene. Same specification as [`GetCurrentScene`](#getcurrentscene).
|
||||
*
|
||||
* @api events
|
||||
* @name PreviewSceneChanged
|
||||
* @category studio mode
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSEvents::SelectedSceneChanged(QListWidgetItem* current, QListWidgetItem* prev) {
|
||||
if (Utils::IsPreviewModeActive()) {
|
||||
obs_scene_t* scene = Utils::SceneListItemToScene(current);
|
||||
if (!scene) return;
|
||||
|
||||
obs_source_t* scene_source = obs_scene_get_source(scene);
|
||||
obs_data_array_t* scene_items = Utils::GetSceneItems(scene_source);
|
||||
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_string(data, "scene-name", obs_source_get_name(scene_source));
|
||||
obs_data_set_array(data, "sources", scene_items);
|
||||
|
||||
broadcastUpdate("PreviewSceneChanged", data);
|
||||
|
||||
obs_data_array_release(scene_items);
|
||||
obs_data_release(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Studio Mode has been enabled or disabled.
|
||||
*
|
||||
* @return {boolean} `new-state` The new enabled state of Studio Mode.
|
||||
*
|
||||
* @api events
|
||||
* @name StudioModeSwitched
|
||||
* @category studio mode
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSEvents::ModeSwitchClicked(bool checked) {
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_bool(data, "new-state", checked);
|
||||
|
||||
broadcastUpdate("StudioModeSwitched", data);
|
||||
obs_data_release(data);
|
||||
}
|
105
WSEvents.h
105
WSEvents.h
@ -1,105 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef WSEVENTS_H
|
||||
#define WSEVENTS_H
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QListWidgetItem>
|
||||
|
||||
#include "WSServer.h"
|
||||
|
||||
class WSEvents : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WSEvents(WSServer* srv);
|
||||
~WSEvents();
|
||||
static void FrontendEventHandler(
|
||||
enum obs_frontend_event event, void* private_data);
|
||||
void connectTransitionSignals(obs_source_t* transition);
|
||||
void connectSceneSignals(obs_source_t* scene);
|
||||
static WSEvents* Instance;
|
||||
|
||||
uint64_t GetStreamingTime();
|
||||
const char* GetStreamingTimecode();
|
||||
uint64_t GetRecordingTime();
|
||||
const char* GetRecordingTimecode();
|
||||
|
||||
private slots:
|
||||
void deferredInitOperations();
|
||||
void StreamStatus();
|
||||
void TransitionDurationChanged(int ms);
|
||||
void SelectedSceneChanged(
|
||||
QListWidgetItem* current, QListWidgetItem* prev);
|
||||
void ModeSwitchClicked(bool checked);
|
||||
|
||||
private:
|
||||
WSServer* _srv;
|
||||
signal_handler_t* transition_handler;
|
||||
signal_handler_t* scene_handler;
|
||||
|
||||
bool _streaming_active;
|
||||
bool _recording_active;
|
||||
|
||||
uint64_t _stream_starttime;
|
||||
uint64_t _rec_starttime;
|
||||
|
||||
uint64_t _lastBytesSent;
|
||||
uint64_t _lastBytesSentTime;
|
||||
|
||||
void broadcastUpdate(const char* updateType,
|
||||
obs_data_t* additionalFields);
|
||||
|
||||
void OnSceneChange();
|
||||
void OnSceneListChange();
|
||||
void OnSceneCollectionChange();
|
||||
void OnSceneCollectionListChange();
|
||||
|
||||
void OnTransitionChange();
|
||||
void OnTransitionListChange();
|
||||
|
||||
void OnProfileChange();
|
||||
void OnProfileListChange();
|
||||
|
||||
void OnStreamStarting();
|
||||
void OnStreamStarted();
|
||||
void OnStreamStopping();
|
||||
void OnStreamStopped();
|
||||
|
||||
void OnRecordingStarting();
|
||||
void OnRecordingStarted();
|
||||
void OnRecordingStopping();
|
||||
void OnRecordingStopped();
|
||||
|
||||
void OnReplayStarting();
|
||||
void OnReplayStarted();
|
||||
void OnReplayStopping();
|
||||
void OnReplayStopped();
|
||||
|
||||
void OnExit();
|
||||
|
||||
static void OnTransitionBegin(void* param, calldata_t* data);
|
||||
|
||||
static void OnSceneReordered(void* param, calldata_t* data);
|
||||
static void OnSceneItemAdd(void* param, calldata_t* data);
|
||||
static void OnSceneItemDelete(void* param, calldata_t* data);
|
||||
static void OnSceneItemVisibilityChanged(void* param, calldata_t* data);
|
||||
};
|
||||
|
||||
#endif // WSEVENTS_H
|
2326
WSRequestHandler.cpp
2326
WSRequestHandler.cpp
File diff suppressed because it is too large
Load Diff
@ -1,124 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef WSREQUESTHANDLER_H
|
||||
#define WSREQUESTHANDLER_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QWebSocket>
|
||||
#include <QWebSocketServer>
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
class WSRequestHandler : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WSRequestHandler(QWebSocket* client);
|
||||
~WSRequestHandler();
|
||||
void processIncomingMessage(QString textMessage);
|
||||
bool hasField(const char* name);
|
||||
|
||||
private:
|
||||
static obs_service_t* _service;
|
||||
QWebSocket* _client;
|
||||
const char* _messageId;
|
||||
const char* _requestType;
|
||||
obs_data_t* data;
|
||||
|
||||
QMap<QString, void(*)(WSRequestHandler*)> messageMap;
|
||||
QSet<QString> authNotRequired;
|
||||
|
||||
void SendOKResponse(obs_data_t* additionalFields = NULL);
|
||||
void SendErrorResponse(const char* errorMessage);
|
||||
void SendResponse(obs_data_t* response);
|
||||
|
||||
static void HandleGetVersion(WSRequestHandler* req);
|
||||
static void HandleGetAuthRequired(WSRequestHandler* req);
|
||||
static void HandleAuthenticate(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetCurrentScene(WSRequestHandler* req);
|
||||
static void HandleGetCurrentScene(WSRequestHandler* req);
|
||||
static void HandleGetSceneList(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetSceneItemRender(WSRequestHandler* req);
|
||||
static void HandleSetSceneItemPosition(WSRequestHandler* req);
|
||||
static void HandleSetSceneItemTransform(WSRequestHandler* req);
|
||||
static void HandleSetSceneItemCrop(WSRequestHandler* req);
|
||||
static void HandleResetSceneItem(WSRequestHandler* req);
|
||||
|
||||
static void HandleGetStreamingStatus(WSRequestHandler* req);
|
||||
static void HandleStartStopStreaming(WSRequestHandler* req);
|
||||
static void HandleStartStopRecording(WSRequestHandler* req);
|
||||
static void HandleStartStreaming(WSRequestHandler* req);
|
||||
static void HandleStopStreaming(WSRequestHandler* req);
|
||||
static void HandleStartRecording(WSRequestHandler* req);
|
||||
static void HandleStopRecording(WSRequestHandler* req);
|
||||
|
||||
static void HandleStartStopReplayBuffer(WSRequestHandler* req);
|
||||
static void HandleStartReplayBuffer(WSRequestHandler* req);
|
||||
static void HandleStopReplayBuffer(WSRequestHandler* req);
|
||||
static void HandleSaveReplayBuffer(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetRecordingFolder(WSRequestHandler* req);
|
||||
static void HandleGetRecordingFolder(WSRequestHandler* req);
|
||||
|
||||
static void HandleGetTransitionList(WSRequestHandler* req);
|
||||
static void HandleGetCurrentTransition(WSRequestHandler* req);
|
||||
static void HandleSetCurrentTransition(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetVolume(WSRequestHandler* req);
|
||||
static void HandleGetVolume(WSRequestHandler* req);
|
||||
static void HandleToggleMute(WSRequestHandler* req);
|
||||
static void HandleSetMute(WSRequestHandler* req);
|
||||
static void HandleGetMute(WSRequestHandler* req);
|
||||
static void HandleSetSyncOffset(WSRequestHandler* req);
|
||||
static void HandleGetSyncOffset(WSRequestHandler* req);
|
||||
static void HandleGetSpecialSources(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetCurrentSceneCollection(WSRequestHandler* req);
|
||||
static void HandleGetCurrentSceneCollection(WSRequestHandler* req);
|
||||
static void HandleListSceneCollections(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetCurrentProfile(WSRequestHandler* req);
|
||||
static void HandleGetCurrentProfile(WSRequestHandler* req);
|
||||
static void HandleListProfiles(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetStreamSettings(WSRequestHandler* req);
|
||||
static void HandleGetStreamSettings(WSRequestHandler* req);
|
||||
static void HandleSaveStreamSettings(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetTransitionDuration(WSRequestHandler* req);
|
||||
static void HandleGetTransitionDuration(WSRequestHandler* req);
|
||||
|
||||
static void HandleGetStudioModeStatus(WSRequestHandler* req);
|
||||
static void HandleGetPreviewScene(WSRequestHandler* req);
|
||||
static void HandleSetPreviewScene(WSRequestHandler* req);
|
||||
static void HandleTransitionToProgram(WSRequestHandler* req);
|
||||
static void HandleEnableStudioMode(WSRequestHandler* req);
|
||||
static void HandleDisableStudioMode(WSRequestHandler* req);
|
||||
static void HandleToggleStudioMode(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetTextGDIPlusProperties(WSRequestHandler* req);
|
||||
static void HandleGetTextGDIPlusProperties(WSRequestHandler* req);
|
||||
static void HandleSetBrowserSourceProperties(WSRequestHandler* req);
|
||||
static void HandleGetBrowserSourceProperties(WSRequestHandler* req);
|
||||
};
|
||||
|
||||
#endif // WSPROTOCOL_H
|
147
WSServer.cpp
147
WSServer.cpp
@ -1,147 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <QtWebSockets/QWebSocket>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include "WSServer.h"
|
||||
#include "obs-websocket.h"
|
||||
#include "Config.h"
|
||||
#include "Utils.h"
|
||||
|
||||
QT_USE_NAMESPACE
|
||||
|
||||
WSServer* WSServer::Instance = nullptr;
|
||||
|
||||
WSServer::WSServer(QObject* parent)
|
||||
: QObject(parent),
|
||||
_wsServer(Q_NULLPTR),
|
||||
_clients(),
|
||||
_clMutex(QMutex::Recursive) {
|
||||
_wsServer = new QWebSocketServer(
|
||||
QStringLiteral("obs-websocket"),
|
||||
QWebSocketServer::NonSecureMode);
|
||||
}
|
||||
|
||||
WSServer::~WSServer() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void WSServer::Start(quint16 port) {
|
||||
if (port == _wsServer->serverPort())
|
||||
return;
|
||||
|
||||
if(_wsServer->isListening())
|
||||
Stop();
|
||||
|
||||
bool serverStarted = _wsServer->listen(QHostAddress::Any, port);
|
||||
if (serverStarted) {
|
||||
connect(_wsServer, SIGNAL(newConnection()),
|
||||
this, SLOT(onNewConnection()));
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::Stop() {
|
||||
_clMutex.lock();
|
||||
for(QWebSocket* pClient : _clients) {
|
||||
pClient->close();
|
||||
}
|
||||
_clMutex.unlock();
|
||||
|
||||
_wsServer->close();
|
||||
}
|
||||
|
||||
void WSServer::broadcast(QString message) {
|
||||
_clMutex.lock();
|
||||
for(QWebSocket* pClient : _clients) {
|
||||
if (Config::Current()->AuthRequired
|
||||
&& (pClient->property(PROP_AUTHENTICATED).toBool() == false)) {
|
||||
// Skip this client if unauthenticated
|
||||
continue;
|
||||
}
|
||||
pClient->sendTextMessage(message);
|
||||
}
|
||||
_clMutex.unlock();
|
||||
}
|
||||
|
||||
void WSServer::onNewConnection() {
|
||||
QWebSocket* pSocket = _wsServer->nextPendingConnection();
|
||||
if (pSocket) {
|
||||
connect(pSocket, SIGNAL(textMessageReceived(const QString&)),
|
||||
this, SLOT(onTextMessageReceived(QString)));
|
||||
connect(pSocket, SIGNAL(disconnected()),
|
||||
this, SLOT(onSocketDisconnected()));
|
||||
|
||||
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
||||
|
||||
_clMutex.lock();
|
||||
_clients << pSocket;
|
||||
_clMutex.unlock();
|
||||
|
||||
QHostAddress clientAddr = pSocket->peerAddress();
|
||||
QString clientIp = Utils::FormatIPAddress(clientAddr);
|
||||
|
||||
blog(LOG_INFO, "new client connection from %s:%d",
|
||||
clientIp.toUtf8().constData(), pSocket->peerPort());
|
||||
|
||||
QString msg = QString(obs_module_text("OBSWebsocket.ConnectNotify.ClientIP"))
|
||||
+ QString(" ")
|
||||
+ Utils::FormatIPAddress(clientAddr);
|
||||
|
||||
Utils::SysTrayNotify(msg,
|
||||
QSystemTrayIcon::Information,
|
||||
QString(obs_module_text("OBSWebsocket.ConnectNotify.Connected")));
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::onTextMessageReceived(QString message) {
|
||||
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
|
||||
if (pSocket) {
|
||||
WSRequestHandler handler(pSocket);
|
||||
handler.processIncomingMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::onSocketDisconnected() {
|
||||
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
|
||||
if (pSocket) {
|
||||
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
||||
|
||||
_clMutex.lock();
|
||||
_clients.removeAll(pSocket);
|
||||
_clMutex.unlock();
|
||||
|
||||
pSocket->deleteLater();
|
||||
|
||||
QHostAddress clientAddr = pSocket->peerAddress();
|
||||
QString clientIp = Utils::FormatIPAddress(clientAddr);
|
||||
|
||||
blog(LOG_INFO, "client %s:%d disconnected",
|
||||
clientIp.toUtf8().constData(), pSocket->peerPort());
|
||||
|
||||
QString msg = QString(obs_module_text("OBSWebsocket.ConnectNotify.ClientIP"))
|
||||
+ QString(" ")
|
||||
+ Utils::FormatIPAddress(clientAddr);
|
||||
|
||||
Utils::SysTrayNotify(msg,
|
||||
QSystemTrayIcon::Information,
|
||||
QString(obs_module_text("OBSWebsocket.ConnectNotify.Disconnected")));
|
||||
}
|
||||
}
|
52
WSServer.h
52
WSServer.h
@ -1,52 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef WSSERVER_H
|
||||
#define WSSERVER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
|
||||
QT_FORWARD_DECLARE_CLASS(QWebSocket)
|
||||
|
||||
class WSServer : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WSServer(QObject* parent = Q_NULLPTR);
|
||||
virtual ~WSServer();
|
||||
void Start(quint16 port);
|
||||
void Stop();
|
||||
void broadcast(QString message);
|
||||
static WSServer* Instance;
|
||||
|
||||
private slots:
|
||||
void onNewConnection();
|
||||
void onTextMessageReceived(QString message);
|
||||
void onSocketDisconnected();
|
||||
|
||||
private:
|
||||
QWebSocketServer* _wsServer;
|
||||
QList<QWebSocket*> _clients;
|
||||
QMutex _clMutex;
|
||||
};
|
||||
|
||||
#endif // WSSERVER_H
|
50
appveyor.yml
50
appveyor.yml
@ -1,50 +0,0 @@
|
||||
environment:
|
||||
CURL_VERSION: 7.39.0
|
||||
|
||||
install:
|
||||
- git submodule update --init --recursive
|
||||
- cd C:\projects\
|
||||
- if not exist dependencies2013.zip curl -kLO https://obsproject.com/downloads/dependencies2013.zip -f --retry 5 -C -
|
||||
- 7z x dependencies2013.zip -odependencies2013
|
||||
- if not exist qt570.zip curl -kLO https://www.slepin.fr/obs-websocket/ci/qt570.zip -f --retry 5 -C -
|
||||
- 7z x qt570.zip -o"Qt5.7.0"
|
||||
- set DepsPath32=%CD%\dependencies2013\win32
|
||||
- set DepsPath64=%CD%\dependencies2013\win64
|
||||
- set QTDIR32=%CD%\Qt5.7.0\msvc2013
|
||||
- set QTDIR64=%CD%\Qt5.7.0\msvc2013_64
|
||||
- set build_config=Release
|
||||
- git clone --recursive https://github.com/jp9000/obs-studio
|
||||
- cd C:\projects\obs-studio\
|
||||
- git checkout 19.0.3
|
||||
- mkdir build
|
||||
- mkdir build32
|
||||
- mkdir build64
|
||||
- cd ./build32
|
||||
- cmake -G "Visual Studio 12 2013" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||
- cd ../build64
|
||||
- cmake -G "Visual Studio 12 2013 Win64" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build32\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build64\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
- cd C:\projects\obs-websocket\
|
||||
- mkdir build32
|
||||
- mkdir build64
|
||||
- cd ./build32
|
||||
- cmake -G "Visual Studio 12 2013" -DQTDIR="%QTDIR32%" -DLibObs_DIR="C:\projects\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
|
||||
- cd ../build64
|
||||
- cmake -G "Visual Studio 12 2013 Win64" -DQTDIR="%QTDIR64%" -DLibObs_DIR="C:\projects\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
|
||||
|
||||
build_script:
|
||||
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build32\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build64\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
|
||||
before_deploy:
|
||||
- 7z a "C:\projects\obs-websocket\build.zip" C:\projects\obs-websocket\release\*
|
||||
|
||||
deploy_script:
|
||||
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\build.zip" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1).zip"
|
||||
|
||||
test: off
|
||||
|
||||
cache:
|
||||
- C:\projects\dependencies2013.zip
|
||||
- C:\projects\qt570.zip
|
183
azure-pipelines.yml
Normal file
183
azure-pipelines.yml
Normal file
@ -0,0 +1,183 @@
|
||||
variables:
|
||||
isReleaseMode: ${{ startsWith(variables['Build.SourceBranch'], 'refs/tags/') }}
|
||||
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- '4.x-current'
|
||||
tags:
|
||||
include:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
- job: 'GenerateDocs'
|
||||
condition: |
|
||||
or(
|
||||
eq(variables['Build.SourceBranch'], 'refs/heads/4.x-current'),
|
||||
eq(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||
)
|
||||
pool:
|
||||
vmImage: 'ubuntu-18.04'
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: false
|
||||
|
||||
- script: ./CI/generate-docs.sh
|
||||
displayName: 'Generate docs'
|
||||
env:
|
||||
CHECKOUT_REF: $(Build.SourceBranch)
|
||||
GH_TOKEN: $(GithubToken)
|
||||
|
||||
- job: 'Build_Windows'
|
||||
pool:
|
||||
vmImage: 'windows-2019'
|
||||
variables:
|
||||
build_config: RelWithDebInfo
|
||||
DepsBasePath: 'D:\obsdependencies'
|
||||
DepsPath32: '$(DepsBasePath)\win32'
|
||||
DepsPath64: '$(DepsBasePath)\win64'
|
||||
QtBaseDir: 'D:\QtDep'
|
||||
QTDIR32: '$(QtBaseDir)\5.10.1\msvc2017'
|
||||
QTDIR64: '$(QtBaseDir)\5.10.1\msvc2017_64'
|
||||
OBSPath: 'D:\obs-studio'
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: true
|
||||
|
||||
- script: ./CI/install-qt-win.cmd
|
||||
displayName: 'Install Qt'
|
||||
env:
|
||||
QtBaseDir: $(QtBaseDir)
|
||||
|
||||
- task: Cache@2
|
||||
displayName: Restore cached OBS Studio dependencies
|
||||
inputs:
|
||||
key: 'obsdeps | "$(Agent.OS)"'
|
||||
restoreKeys: |
|
||||
obsdeps | "$(Agent.OS)"
|
||||
path: $(DepsBasePath)
|
||||
|
||||
- script: ./CI/download-obs-deps.cmd
|
||||
displayName: 'Download OBS Studio dependencies'
|
||||
|
||||
- task: Cache@2
|
||||
displayName: Restore cached OBS Studio builds
|
||||
inputs:
|
||||
key: 'obs | "$(Agent.OS)"'
|
||||
restoreKeys: |
|
||||
obs | "$(Agent.OS)"
|
||||
path: $(OBSPath)
|
||||
|
||||
- script: ./CI/prepare-obs-windows.cmd
|
||||
displayName: 'Checkout & CMake OBS Studio'
|
||||
env:
|
||||
build_config: $(build_config)
|
||||
DepsPath32: $(DepsPath32)
|
||||
DepsPath64: $(DepsPath64)
|
||||
QTDIR32: $(QTDIR32)
|
||||
QTDIR64: $(QTDIR64)
|
||||
OBSPath: $(OBSPath)
|
||||
|
||||
- task: MSBuild@1
|
||||
displayName: 'Build OBS Studio 32-bit'
|
||||
inputs:
|
||||
msbuildArguments: '/m /p:Configuration=$(build_config)'
|
||||
solution: '$(OBSPath)\build32\obs-studio.sln'
|
||||
|
||||
- task: MSBuild@1
|
||||
displayName: 'Build OBS Studio 64-bit'
|
||||
inputs:
|
||||
msbuildArguments: '/m /p:Configuration=$(build_config)'
|
||||
solution: '$(OBSPath)\build64\obs-studio.sln'
|
||||
|
||||
- script: ./CI/prepare-windows.cmd
|
||||
displayName: 'CMake obs-websocket'
|
||||
env:
|
||||
build_config: $(build_config)
|
||||
QTDIR32: $(QTDIR32)
|
||||
QTDIR64: $(QTDIR64)
|
||||
OBSPath: $(OBSPath)
|
||||
|
||||
- task: MSBuild@1
|
||||
displayName: 'Build obs-websocket 32-bit'
|
||||
inputs:
|
||||
msbuildArguments: '/m /p:Configuration=$(build_config)'
|
||||
solution: '.\build32\obs-websocket.sln'
|
||||
|
||||
- task: MSBuild@1
|
||||
displayName: 'Build obs-websocket 64-bit'
|
||||
inputs:
|
||||
msbuildArguments: '/m /p:Configuration=$(build_config)'
|
||||
solution: '.\build64\obs-websocket.sln'
|
||||
|
||||
- script: ./CI/package-windows.cmd
|
||||
displayName: 'Package obs-websocket'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Upload package artifacts'
|
||||
inputs:
|
||||
pathtoPublish: './package'
|
||||
artifactName: 'windows_build'
|
||||
|
||||
- job: 'Build_Linux'
|
||||
pool:
|
||||
vmImage: 'ubuntu-18.04'
|
||||
variables:
|
||||
BUILD_REASON: $(Build.Reason)
|
||||
BRANCH_SHORT_NAME: $(Build.SourceBranchName)
|
||||
BRANCH_FULL_NAME: $(Build.SourceBranch)
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: true
|
||||
|
||||
- script: ./CI/install-dependencies-ubuntu.sh
|
||||
displayName: 'Install dependencies'
|
||||
|
||||
- script: ./CI/build-ubuntu.sh
|
||||
displayName: 'Build obs-websocket'
|
||||
|
||||
- script: ./CI/package-ubuntu.sh
|
||||
displayName: 'Package obs-websocket'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
pathtoPublish: './package'
|
||||
artifactName: 'deb_build'
|
||||
|
||||
- job: 'Build_macOS'
|
||||
pool:
|
||||
vmImage: 'macos-10.14'
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: true
|
||||
|
||||
- script: ./CI/install-dependencies-macos.sh
|
||||
displayName: 'Install dependencies'
|
||||
|
||||
- script: ./CI/install-build-obs-macos.sh
|
||||
displayName: 'Build OBS'
|
||||
|
||||
- script: ./CI/build-macos.sh
|
||||
displayName: 'Build obs-websocket'
|
||||
|
||||
- task: InstallAppleCertificate@2
|
||||
displayName: 'Install release signing certificates'
|
||||
condition: eq(variables['isReleaseMode'], true)
|
||||
inputs:
|
||||
certSecureFile: 'Certificates.p12'
|
||||
certPwd: $(secrets.macOS.certificatesImportPassword)
|
||||
|
||||
- script: ./CI/package-macos.sh
|
||||
displayName: 'Package obs-websocket'
|
||||
env:
|
||||
RELEASE_MODE: $(isReleaseMode)
|
||||
CODE_SIGNING_IDENTITY: $(secrets.macOS.codeSigningIdentity)
|
||||
INSTALLER_SIGNING_IDENTITY: $(secrets.macOS.installerSigningIdentity)
|
||||
AC_USERNAME: $(secrets.macOS.notarization.username)
|
||||
AC_PASSWORD: $(secrets.macOS.notarization.password)
|
||||
AC_PROVIDER_SHORTNAME: $(secrets.macOS.notarization.providerShortName)
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
pathtoPublish: './release'
|
||||
artifactName: 'macos_build'
|
3
crowdin.yml
Normal file
3
crowdin.yml
Normal file
@ -0,0 +1,3 @@
|
||||
files:
|
||||
- source: /data/locale/en-US.ini
|
||||
translation: /data/locale/%locale%.ini
|
1
data/locale/ar-SA.ini
Normal file
1
data/locale/ar-SA.ini
Normal file
@ -0,0 +1 @@
|
||||
|
@ -1,6 +1,18 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Websocket-Server Einstellungen"
|
||||
OBSWebsocket.Settings.DialogTitle="Websocket-Server Einstellungen"
|
||||
OBSWebsocket.Settings.ServerEnable="Websocket-Server aktivieren"
|
||||
OBSWebsocket.Settings.ServerPort="Server Port"
|
||||
OBSWebsocket.Settings.AuthRequired="Authentifizierung erforderlich"
|
||||
OBSWebsocket.Settings.DialogTitle="WebSockets-Servereinstellungen"
|
||||
OBSWebsocket.Settings.ServerEnable="WebSockets-Server aktivieren"
|
||||
OBSWebsocket.Settings.ServerPort="Server-Port"
|
||||
OBSWebsocket.Settings.AuthRequired="Authentifizierung aktivieren"
|
||||
OBSWebsocket.Settings.Password="Passwort"
|
||||
OBSWebsocket.Settings.LockToIPv4="Nur IPv4 verwenden (deaktiviert IPv6)"
|
||||
OBSWebsocket.Settings.DebugEnable="Debug-Protokollierung aktivieren"
|
||||
OBSWebsocket.Settings.AlertsEnable="Infobereichbenachrichtigungen aktivieren"
|
||||
OBSWebsocket.NotifyConnect.Title="Neue Websocket-Verbindung"
|
||||
OBSWebsocket.NotifyConnect.Message="Client %1 verbunden"
|
||||
OBSWebsocket.NotifyDisconnect.Title="Websocket-Client getrennt"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Client %1 getrennt"
|
||||
OBSWebsocket.Server.StartFailed.Title="Websocket-Serverfehler"
|
||||
OBSWebsocket.Server.StartFailed.Message="Der WebSocket-Server konnte nicht gestartet werden, mögliche Gründe:\n - Der TCP-Port %1 wird möglicherweise gerade von einem anderen Programm verwendet. Versuchen Sie einen anderen Port in den Websocket-Servereinstellungen zu setzen oder alle Programme zu beenden, die den Port möglicherweise verwenden.\n - Fehler: %2"
|
||||
OBSWebsocket.ProfileChanged.Started="WebSockets-Server in diesem Profil aktiviert. Server gestartet."
|
||||
OBSWebsocket.ProfileChanged.Stopped="WebSockets-Server in diesem Profil deaktiviert. Server gestoppt."
|
||||
OBSWebsocket.ProfileChanged.Restarted="WebSockets-Server in diesem Profil geändert. Server startet neu."
|
||||
|
||||
|
@ -1,10 +1,21 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Websocket server settings"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Enable Websocket server"
|
||||
OBSWebsocket.Settings.DialogTitle="WebSockets Server Settings"
|
||||
OBSWebsocket.Settings.ServerEnable="Enable WebSockets server"
|
||||
OBSWebsocket.Settings.ServerPort="Server Port"
|
||||
OBSWebsocket.Settings.AuthRequired="Enable authentication"
|
||||
OBSWebsocket.Settings.Password="Password"
|
||||
OBSWebsocket.Settings.LockToIPv4="Lock server to only using IPv4"
|
||||
OBSWebsocket.Settings.DebugEnable="Enable debug logging"
|
||||
OBSWebsocket.ConnectNotify.Connected="New WebSocket connection"
|
||||
OBSWebsocket.ConnectNotify.Disconnected="WebSocket client disconnected"
|
||||
OBSWebsocket.ConnectNotify.ClientIP="Client Address:"
|
||||
OBSWebsocket.Settings.AlertsEnable="Enable System Tray Alerts"
|
||||
OBSWebsocket.Settings.AuthDisabledWarning="Running obs-websocket with authentication disabled is not recommended, as it allows attackers to easily collect sensetive data. Are you sure you want to proceed?"
|
||||
OBSWebsocket.NotifyConnect.Title="New WebSocket connection"
|
||||
OBSWebsocket.NotifyConnect.Message="Client %1 connected"
|
||||
OBSWebsocket.NotifyDisconnect.Title="WebSocket client disconnected"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Client %1 disconnected"
|
||||
OBSWebsocket.Server.StartFailed.Title="WebSockets Server failure"
|
||||
OBSWebsocket.Server.StartFailed.Message="The WebSockets server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - Error message: %2"
|
||||
OBSWebsocket.ProfileChanged.Started="WebSockets server enabled in this profile. Server started."
|
||||
OBSWebsocket.ProfileChanged.Stopped="WebSockets server disabled in this profile. Server stopped."
|
||||
OBSWebsocket.ProfileChanged.Restarted="WebSockets server port changed in this profile. Server restarted."
|
||||
OBSWebsocket.InitialPasswordSetup.Title="obs-websocket - Server Password Configuration"
|
||||
OBSWebsocket.InitialPasswordSetup.Text="It looks like you are running obs-websocket for the first time. Do you want to configure a password now for the WebSockets server? Setting a password is highly recommended."
|
||||
OBSWebsocket.InitialPasswordSetup.DismissedText="You can configure a server password later in the WebSockets Server Settings. (Under the Tools menu of OBS Studio)"
|
||||
|
16
data/locale/es-ES.ini
Normal file
16
data/locale/es-ES.ini
Normal file
@ -0,0 +1,16 @@
|
||||
OBSWebsocket.Settings.DialogTitle="Configuración del servidor WebSocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Habilitar el servidor WebSockets"
|
||||
OBSWebsocket.Settings.ServerPort="Puerto del Servidor"
|
||||
OBSWebsocket.Settings.AuthRequired="Habilitar autenticación"
|
||||
OBSWebsocket.Settings.Password="Contraseña"
|
||||
OBSWebsocket.Settings.DebugEnable="Habilitar registro de depuración"
|
||||
OBSWebsocket.Settings.AlertsEnable="Habilitar alertas en la bandeja de sistema"
|
||||
OBSWebsocket.NotifyConnect.Title="Nueva conexión WebSocket"
|
||||
OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado"
|
||||
OBSWebsocket.NotifyDisconnect.Title="Cliente WebSocket desconectado"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado"
|
||||
OBSWebsocket.Server.StartFailed.Title="Falla en el servidor WebSockets"
|
||||
OBSWebsocket.ProfileChanged.Started="El servidor WebSocket esta habilitado en este perfil. El servidor ha iniciado."
|
||||
OBSWebsocket.ProfileChanged.Stopped="Servidor WebSockets deshabilitado en este perfil. Servidor detenido."
|
||||
OBSWebsocket.ProfileChanged.Restarted="Puerto del servidor WebSockets cambiado en este perfil. Servidor reiniciado."
|
||||
|
@ -1,9 +1,16 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Paramètres du serveur Websocket"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Activer le serveur Websockets"
|
||||
OBSWebsocket.Settings.DialogTitle="Paramètres du serveur WebSockets"
|
||||
OBSWebsocket.Settings.ServerEnable="Activer le serveur WebSocket"
|
||||
OBSWebsocket.Settings.ServerPort="Port du serveur"
|
||||
OBSWebsocket.Settings.AuthRequired="Activer l'authentification"
|
||||
OBSWebsocket.Settings.Password="Mot de passe"
|
||||
OBSWebsocket.ConnectNotify.Connected="Nouvelle connexion WebSocket"
|
||||
OBSWebsocket.ConnectNotify.Disconnected="Déconnexion WebSocket"
|
||||
OBSWebsocket.ConnectNotify.ClientIP="Adresse du client :"
|
||||
OBSWebsocket.Settings.DebugEnable="Débogage dans le fichier journal"
|
||||
OBSWebsocket.Settings.AlertsEnable="Notifications de connexion/déconnexion"
|
||||
OBSWebsocket.NotifyConnect.Title="Nouvelle connexion WebSocket"
|
||||
OBSWebsocket.NotifyConnect.Message="Le client %1 s'est connecté"
|
||||
OBSWebsocket.NotifyDisconnect.Title="Déconnexion WebSocket"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Le client %1 s'est déconnecté"
|
||||
OBSWebsocket.Server.StartFailed.Title="Impossible de démarrer le serveur WebSockets"
|
||||
OBSWebsocket.ProfileChanged.Started="Serveur WebSockets actif dans ce profil."
|
||||
OBSWebsocket.ProfileChanged.Stopped="Serveur WebSockets désactivé dans ce profil."
|
||||
OBSWebsocket.ProfileChanged.Restarted="Le port actuel diffère du port configuré dans ce profil. Serveur WebSockets redémarré."
|
||||
|
||||
|
1
data/locale/hi-IN.ini
Normal file
1
data/locale/hi-IN.ini
Normal file
@ -0,0 +1 @@
|
||||
|
18
data/locale/it-IT.ini
Normal file
18
data/locale/it-IT.ini
Normal file
@ -0,0 +1,18 @@
|
||||
OBSWebsocket.Settings.DialogTitle="Impostazioni del server di WebSocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Abilitare il server WebSockets"
|
||||
OBSWebsocket.Settings.ServerPort="Porta del server"
|
||||
OBSWebsocket.Settings.AuthRequired="Abilitare l'autenticazione"
|
||||
OBSWebsocket.Settings.Password="Password"
|
||||
OBSWebsocket.Settings.LockToIPv4="Blocca il server per usare solo IPv4"
|
||||
OBSWebsocket.Settings.DebugEnable="Attivare la registrazione di debug"
|
||||
OBSWebsocket.Settings.AlertsEnable="Attivare gli avvisi di vassoio di sistema"
|
||||
OBSWebsocket.NotifyConnect.Title="Nuova connessione WebSocket"
|
||||
OBSWebsocket.NotifyConnect.Message="%1 cliente collegato"
|
||||
OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente disconnesso"
|
||||
OBSWebsocket.NotifyDisconnect.Message="%1 cliente disconnesso"
|
||||
OBSWebsocket.Server.StartFailed.Title="Errore del WebSocket Server"
|
||||
OBSWebsocket.Server.StartFailed.Message="L'avvio del server WebSockets è fallito, forse perché:\n - La porta TCP %1 può attualmente essere in uso altrove su questo sistema, forse da un'altra applicazione. Provare a impostare una porta TCP diversa nelle impostazioni del server WebSocket o interrompere qualsiasi applicazione che potrebbe utilizzare questa porta.\n - Messaggio di errore: %2"
|
||||
OBSWebsocket.ProfileChanged.Started="Server WebSockets abilitato in questo profilo. Il server è avviato."
|
||||
OBSWebsocket.ProfileChanged.Stopped="Server WebSocket disabilitato in questo profilo. Server interrotto."
|
||||
OBSWebsocket.ProfileChanged.Restarted="La porta del server WebSocket è stata modificata in questo profilo. Il server è stato riavviato."
|
||||
|
16
data/locale/ja-JP.ini
Normal file
16
data/locale/ja-JP.ini
Normal file
@ -0,0 +1,16 @@
|
||||
OBSWebsocket.Settings.DialogTitle="Websocket サーバー設定"
|
||||
OBSWebsocket.Settings.ServerEnable="WebSockets サーバーを有効にする"
|
||||
OBSWebsocket.Settings.ServerPort="サーバーポート"
|
||||
OBSWebsocket.Settings.AuthRequired="認証を有効にする"
|
||||
OBSWebsocket.Settings.Password="パスワード"
|
||||
OBSWebsocket.Settings.DebugEnable="デバッグログを有効にする"
|
||||
OBSWebsocket.Settings.AlertsEnable="システムトレイ通知を有効にする"
|
||||
OBSWebsocket.NotifyConnect.Title="新しい WebSocket 接続"
|
||||
OBSWebsocket.NotifyConnect.Message="接続されているクライアント %1"
|
||||
OBSWebsocket.NotifyDisconnect.Title="WebSocket クライアントが切断されました"
|
||||
OBSWebsocket.NotifyDisconnect.Message="切断されたクライアント %1"
|
||||
OBSWebsocket.Server.StartFailed.Title="WebSockets サーバー障害"
|
||||
OBSWebsocket.ProfileChanged.Started="このプロファイルでWebSocketサーバが有効になりました。サーバを起動しました。"
|
||||
OBSWebsocket.ProfileChanged.Stopped="このプロファイルでWebSocketサーバが無効になりました。サーバを停止しました。"
|
||||
OBSWebsocket.ProfileChanged.Restarted="このプロファイルでWebSocketサーバの通信ポートが変更されました。サーバを再起動しました。"
|
||||
|
18
data/locale/ko-KR.ini
Normal file
18
data/locale/ko-KR.ini
Normal file
@ -0,0 +1,18 @@
|
||||
OBSWebsocket.Settings.DialogTitle="웹소켓 서버 설정"
|
||||
OBSWebsocket.Settings.ServerEnable="웹소켓 서버 활성화"
|
||||
OBSWebsocket.Settings.ServerPort="서버 포트"
|
||||
OBSWebsocket.Settings.AuthRequired="인증 활성화"
|
||||
OBSWebsocket.Settings.Password="비밀번호"
|
||||
OBSWebsocket.Settings.LockToIPv4="IPv4만 이용하여 서버를 연결"
|
||||
OBSWebsocket.Settings.DebugEnable="디버그 로깅 활성화"
|
||||
OBSWebsocket.Settings.AlertsEnable="시스템 트레이 알림 활성화"
|
||||
OBSWebsocket.NotifyConnect.Title="새로운 웹소켓 연결"
|
||||
OBSWebsocket.NotifyConnect.Message="클라이언트 %1 가 연결되었습니다"
|
||||
OBSWebsocket.NotifyDisconnect.Title="웹소켓 클라이언트가 연결 해제되었습니다"
|
||||
OBSWebsocket.NotifyDisconnect.Message="클라이언트 %1 가 연결이 해제되었습니다"
|
||||
OBSWebsocket.Server.StartFailed.Title="웹소켓 서버 시작이 실패하였습니다"
|
||||
OBSWebsocket.Server.StartFailed.Message="웹소켓 서버가 시작되지 못했습니다. \n 시스템 상의 다른 프로그램이 TCP 포트 %1을 사용 중인 것으로 보입니다. 다른 TCP 포트를 사용하거나, 해당 포트를 사용 중인 프로그램을 종료하고 다시 시도해주세요. \n 에러 메시지: %2"
|
||||
OBSWebsocket.ProfileChanged.Started="프로파일 설정에 따라 웹소켓 서버가 활성화되었습니다. 서버가 시작되었습니다."
|
||||
OBSWebsocket.ProfileChanged.Stopped="프로파일 설정에 따라 웹소켓 서버가 비활성화되었습니다. 서버가 중지되었습니다."
|
||||
OBSWebsocket.ProfileChanged.Restarted="프로파일 설정에 따라 웹소켓 포트가 변경되었습니다. 서버가 재시작되었습니다."
|
||||
|
@ -1,6 +1,18 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Websocket server instellingen"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Activeer Websocket server"
|
||||
OBSWebsocket.Settings.ServerPort="Server Poort"
|
||||
OBSWebsocket.Settings.AuthRequired="Activeer authenticatie"
|
||||
OBSWebsocket.Settings.Password="Wachtwoord"
|
||||
OBSWebsocket.Settings.DialogTitle="WebSockets Server Instellingen"
|
||||
OBSWebsocket.Settings.ServerEnable="WebSockets server inschakelen"
|
||||
OBSWebsocket.Settings.ServerPort="Serverpoort"
|
||||
OBSWebsocket.Settings.AuthRequired="Verificatie inschakelen"
|
||||
OBSWebsocket.Settings.Password="Wachtwoord"
|
||||
OBSWebsocket.Settings.LockToIPv4="Server vergrendelen om alleen IPv4 te gebruiken"
|
||||
OBSWebsocket.Settings.DebugEnable="Activeer debug logs"
|
||||
OBSWebsocket.Settings.AlertsEnable="Systemvak waarschuwingen inschakelen"
|
||||
OBSWebsocket.NotifyConnect.Title="Nieuwe WebSocket verbinding"
|
||||
OBSWebsocket.NotifyConnect.Message="Client %1 verbonden"
|
||||
OBSWebsocket.NotifyDisconnect.Title="WebSocket client connectie verbroken"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Client %1 losgekoppeld"
|
||||
OBSWebsocket.Server.StartFailed.Title="Fout in WebSocket server"
|
||||
OBSWebsocket.Server.StartFailed.Message="De obs-websocket server kan niet worden gestart, misschien omdat: \n - TCP-poort %1 momenteel elders wordt gebruikt op dit systeem, eventueel door een andere toepassing. Probeer een andere TCP-poort in te stellen in de WebSocket Server-instellingen of stop elke toepassing die deze poort zou kunnen gebruiken.\n Een onbekende Netwerkfout op uw systeem. Probeer het opnieuw door de instellingen te wijzigen, OBS te herstarten of uw systeem te herstarten."
|
||||
OBSWebsocket.ProfileChanged.Started="WebSockets server ingeschakeld in dit profiel. Server gestart."
|
||||
OBSWebsocket.ProfileChanged.Stopped="WebSockets server uitgeschakeld in dit profiel. Server is gestopt."
|
||||
OBSWebsocket.ProfileChanged.Restarted="WebSockets server poort is veranderd in dit profiel. Server is herstart."
|
||||
|
||||
|
@ -1,6 +1,15 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Ustawienia serwera zdalnego sterowania"
|
||||
OBSWebsocket.Settings.DialogTitle="Serwer zdalnego sterowania"
|
||||
OBSWebsocket.Settings.ServerEnable="Włącz serwer zdalnego sterowania (Websocket)"
|
||||
OBSWebsocket.Settings.DialogTitle="Ustawienia serwera WebSockets"
|
||||
OBSWebsocket.Settings.ServerEnable="Włącz serwer WebSockets"
|
||||
OBSWebsocket.Settings.ServerPort="Port serwera"
|
||||
OBSWebsocket.Settings.AuthRequired="Wymagaj hasła"
|
||||
OBSWebsocket.Settings.AuthRequired="Wymagaj uwierzytelniania"
|
||||
OBSWebsocket.Settings.Password="Hasło"
|
||||
OBSWebsocket.Settings.LockToIPv4="Zablokuj serwer tylko za pomocą IPv4"
|
||||
OBSWebsocket.Settings.DebugEnable="Włącz rejestrowanie debugowania"
|
||||
OBSWebsocket.Settings.AlertsEnable="Włącz powiadomienia w zasobniku systemowym"
|
||||
OBSWebsocket.NotifyConnect.Title="Nowe połączenie WebSocket"
|
||||
OBSWebsocket.NotifyConnect.Message="Klient %1 połączony"
|
||||
OBSWebsocket.NotifyDisconnect.Title="Klient WebSocket odłączony"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Klient %1 rozłączony"
|
||||
OBSWebsocket.Server.StartFailed.Title="Awaria serwera WebSockets"
|
||||
OBSWebsocket.ProfileChanged.Started="Serwer WebSockets włączony w tym profilu. Serwer uruchomiony."
|
||||
|
||||
|
@ -1,6 +0,0 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Configuraçes do Servidor Websocket"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Habilitar o Servidor Websocket"
|
||||
OBSWebsocket.Settings.ServerPort="Porta do Servidor"
|
||||
OBSWebsocket.Settings.AuthRequired="Autenticação Requerida"
|
||||
OBSWebsocket.Settings.Password="Senha"
|
18
data/locale/pt-PT.ini
Normal file
18
data/locale/pt-PT.ini
Normal file
@ -0,0 +1,18 @@
|
||||
OBSWebsocket.Settings.DialogTitle="Configurações do servidor de WebSockets"
|
||||
OBSWebsocket.Settings.ServerEnable="Habilitar servidor de WebSockets"
|
||||
OBSWebsocket.Settings.ServerPort="Porta do Servidor"
|
||||
OBSWebsocket.Settings.AuthRequired="Activar autenticação"
|
||||
OBSWebsocket.Settings.Password="Palavra passe"
|
||||
OBSWebsocket.Settings.LockToIPv4="Forçar apenas o uso de IPv4 (desabilitar IPv6)"
|
||||
OBSWebsocket.Settings.DebugEnable="Habilitar registro de debug"
|
||||
OBSWebsocket.Settings.AlertsEnable="Ativar Alertas da bandeja do sistema"
|
||||
OBSWebsocket.NotifyConnect.Title="Nova conexão WebSocket"
|
||||
OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado"
|
||||
OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente desconectado"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado"
|
||||
OBSWebsocket.Server.StartFailed.Title="Falha do servidor de WebSocket"
|
||||
OBSWebsocket.Server.StartFailed.Message="O servidor de WebSockets falhou ao iniciar, possivelmente porque:\n - A porta TCP %1 pode já estar em uso em algum outro lugar neste sistema, possivelmente por outro aplicativo. Tente definir uma porta TCP diferente nas configurações do servidor de WebSockets, ou pare qualquer aplicativo que possa estar usando essa porta.\n - Mensagem de erro: %2"
|
||||
OBSWebsocket.ProfileChanged.Started="Servidor de WebSockets habilitado nesse perfil. Servidor iniciado."
|
||||
OBSWebsocket.ProfileChanged.Stopped="Servidor de WebSockets desabilitado nesse perfil. Servidor parado."
|
||||
OBSWebsocket.ProfileChanged.Restarted="Porta do servidor de WebSockets foi alterada neste perfil. Servidor reiniciado."
|
||||
|
18
data/locale/ru-RU.ini
Normal file
18
data/locale/ru-RU.ini
Normal file
@ -0,0 +1,18 @@
|
||||
OBSWebsocket.Settings.DialogTitle="Настройки сервера WebSockets"
|
||||
OBSWebsocket.Settings.ServerEnable="Включить сервер WebSockets"
|
||||
OBSWebsocket.Settings.ServerPort="Порт сервера"
|
||||
OBSWebsocket.Settings.AuthRequired="Включить авторизацию"
|
||||
OBSWebsocket.Settings.Password="Пароль"
|
||||
OBSWebsocket.Settings.LockToIPv4="Блокировка сервера только с использованием IPv4"
|
||||
OBSWebsocket.Settings.DebugEnable="Включить ведение журнала отладки"
|
||||
OBSWebsocket.Settings.AlertsEnable="Включить оповещения в системном трее"
|
||||
OBSWebsocket.NotifyConnect.Title="Новое соединение WebSocket"
|
||||
OBSWebsocket.NotifyConnect.Message="Клиент %1 подключен"
|
||||
OBSWebsocket.NotifyDisconnect.Title="Клиент WebSocket отключён"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Клиент %1 отключен"
|
||||
OBSWebsocket.Server.StartFailed.Title="Сбой сервера WebSockets"
|
||||
OBSWebsocket.Server.StartFailed.Message="Сервер WebSockets не запустился, возможно, потому, что:\n-TCP-порт %1 в настоящее время может использоваться в другом месте этой системы, возможно, другим приложением. Попробуйте установить другой TCP-порт в настройках сервера WebSocket или остановить любое приложение, которое может использовать этот порт.\n-сообщение об ошибке: %2"
|
||||
OBSWebsocket.ProfileChanged.Started="Сервер WebSockets включен в этом профиле. Сервер запущен."
|
||||
OBSWebsocket.ProfileChanged.Stopped="Сервер WebSockets отключен в этом профиле. Сервер остановлен."
|
||||
OBSWebsocket.ProfileChanged.Restarted="Порт сервера WebSockets изменен в этом профиле. Сервер перезапущен."
|
||||
|
@ -1,6 +1,16 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Websocket 服务器设置"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket 设置"
|
||||
OBSWebsocket.Settings.ServerEnable="启用 Websocket 服务器"
|
||||
OBSWebsocket.Settings.DialogTitle="WebSockets 服务器设置"
|
||||
OBSWebsocket.Settings.ServerEnable="启用 WebSockets 服务器"
|
||||
OBSWebsocket.Settings.ServerPort="服务器端口"
|
||||
OBSWebsocket.Settings.AuthRequired="启用密码认证"
|
||||
OBSWebsocket.Settings.Password="密码"
|
||||
OBSWebsocket.Settings.AuthRequired="启用身份验证"
|
||||
OBSWebsocket.Settings.Password="密码"
|
||||
OBSWebsocket.Settings.DebugEnable="启用调试日志"
|
||||
OBSWebsocket.Settings.AlertsEnable="启用系统托盘通知"
|
||||
OBSWebsocket.NotifyConnect.Title="新 WebSocket 连接"
|
||||
OBSWebsocket.NotifyConnect.Message="客户端 %1 已连接"
|
||||
OBSWebsocket.NotifyDisconnect.Title="WebSocket 客户端已断开"
|
||||
OBSWebsocket.NotifyDisconnect.Message="客户端 %1 已断开连接"
|
||||
OBSWebsocket.Server.StartFailed.Title="WebSockets 服务器错误"
|
||||
OBSWebsocket.ProfileChanged.Started="此配置文件中启用了 WebSockets 服务器。服务器已启动。"
|
||||
OBSWebsocket.ProfileChanged.Stopped="此配置文件中禁用了 WebSockets 服务器。服务器已停止。"
|
||||
OBSWebsocket.ProfileChanged.Restarted="此配置文件中的 WebSockets 服务器端口已更改。服务器已重新启动。"
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Websocket 伺服器設定"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket 設定"
|
||||
OBSWebsocket.Settings.ServerEnable="啟用 Websocket 伺服器"
|
||||
OBSWebsocket.Settings.ServerPort="伺服器端口"
|
||||
OBSWebsocket.Settings.AuthRequired="啟用密碼認證"
|
||||
OBSWebsocket.Settings.Password="密碼"
|
||||
OBSWebsocket.Settings.ServerPort="伺服器連接埠"
|
||||
OBSWebsocket.Settings.DebugEnable="啟用除錯日誌"
|
||||
OBSWebsocket.Settings.AlertsEnable="啟用系統列通知"
|
||||
OBSWebsocket.NotifyConnect.Title="新的 WebSocket 連線"
|
||||
OBSWebsocket.NotifyConnect.Message="客戶端 %1 已連線"
|
||||
OBSWebsocket.NotifyDisconnect.Title="WebSocket 客戶端已離線"
|
||||
OBSWebsocket.NotifyDisconnect.Message="客戶端 %1 已離線"
|
||||
OBSWebsocket.Server.StartFailed.Title="WebSocket 伺服器錯誤"
|
||||
|
||||
|
1
deps/asio
vendored
Submodule
1
deps/asio
vendored
Submodule
Submodule deps/asio added at b73dc1d2c0
1
deps/mbedtls
vendored
1
deps/mbedtls
vendored
Submodule deps/mbedtls deleted from 1a6a15c795
1
deps/websocketpp
vendored
Submodule
1
deps/websocketpp
vendored
Submodule
Submodule deps/websocketpp added at c6d7e295bf
@ -1,6 +1,8 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const parseComments = require('parse-comments');
|
||||
const config = require('./config.json');
|
||||
|
||||
/**
|
||||
* Read each file and call `parse-comments` on it.
|
||||
@ -25,9 +27,23 @@ const parseFiles = files => {
|
||||
*/
|
||||
const processComments = comments => {
|
||||
let sorted = {};
|
||||
let errors = [];
|
||||
|
||||
comments.forEach(comment => {
|
||||
if (comment.typedef) {
|
||||
comment.comment = undefined;
|
||||
comment.context = undefined;
|
||||
sorted['typedefs'] = sorted['typedefs'] || [];
|
||||
sorted['typedefs'].push(comment);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof comment.api === 'undefined') return;
|
||||
let validationFailures = validateComment(comment);
|
||||
|
||||
if (validationFailures) {
|
||||
errors.push(validationFailures);
|
||||
}
|
||||
|
||||
// Store the object based on its api (ie. requests, events) and category (ie. general, scenes, etc).
|
||||
comment.category = comment.category || 'miscellaneous';
|
||||
@ -44,9 +60,45 @@ const processComments = comments => {
|
||||
sorted[comment.api][comment.category].push(comment);
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
throw JSON.stringify(errors, null, 2);
|
||||
}
|
||||
|
||||
return sorted;
|
||||
};
|
||||
|
||||
const files = glob.sync("./../*.@(cpp|h)");
|
||||
// Rudimentary validation of documentation content, returns an error object or undefined.
|
||||
const validateComment = comment => {
|
||||
let errors = [];
|
||||
[].concat(comment.params).concat(comment.returns).filter(Boolean).forEach(param => {
|
||||
if (typeof param.name !== 'string' || param.name === '') {
|
||||
errors.push({
|
||||
description: `Invalid param or return value name`,
|
||||
param: param
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof param.type !== 'string' || param.type === '') {
|
||||
errors.push({
|
||||
description: `Invalid param or return value type`,
|
||||
param: param
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
return {
|
||||
errors: errors,
|
||||
fullContext: Object.assign({}, comment)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const files = glob.sync(config.srcGlob);
|
||||
const comments = processComments(parseFiles(files));
|
||||
fs.writeFileSync('./generated/comments.json', JSON.stringify(comments, null, 2));
|
||||
|
||||
if (!fs.existsSync(config.outDirectory)){
|
||||
fs.mkdirSync(config.outDirectory);
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(config.outDirectory, 'comments.json'), JSON.stringify(comments, null, 2));
|
||||
|
5
docs/config.json
Normal file
5
docs/config.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"srcGlob": "./../src/**/*.@(cpp|h)",
|
||||
"srcTemplate": "./protocol.hbs",
|
||||
"outDirectory": "./generated"
|
||||
}
|
16
docs/docs.js
16
docs/docs.js
@ -1,14 +1,16 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const toc = require('markdown-toc');
|
||||
const handlebars = require('handlebars');
|
||||
const config = require('./config.json');
|
||||
|
||||
const helpers = require('handlebars-helpers')({
|
||||
handlebars: handlebars
|
||||
handlebars: handlebars
|
||||
});
|
||||
|
||||
// Allows pipe characters to be used within markdown tables.
|
||||
handlebars.registerHelper('depipe', (text) => {
|
||||
return text.replace('|', `\\|`);
|
||||
return typeof text === 'string' ? text.replace('|', '\\|') : text;
|
||||
});
|
||||
|
||||
const insertHeader = (text) => {
|
||||
@ -26,6 +28,10 @@ const generateProtocol = (templatePath, data) => {
|
||||
return insertHeader(toc.insert(generated));
|
||||
};
|
||||
|
||||
const comments = fs.readFileSync('./generated/comments.json', 'utf8');
|
||||
const markdown = generateProtocol('./protocol.hbs', JSON.parse(comments));
|
||||
fs.writeFileSync('./generated/protocol.md', markdown);
|
||||
if (!fs.existsSync(config.outDirectory)){
|
||||
fs.mkdirSync(config.outDirectory);
|
||||
}
|
||||
|
||||
const comments = fs.readFileSync(path.join(config.outDirectory, 'comments.json'), 'utf8');
|
||||
const markdown = generateProtocol(config.srcTemplate, JSON.parse(comments));
|
||||
fs.writeFileSync(path.join(config.outDirectory, 'protocol.md'), markdown);
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,13 @@
|
||||
# obs-websocket 4.2.0 protocol reference
|
||||
|
||||
**This is the reference for obs-websocket 4.2.0. See the list below for older versions.**
|
||||
- [4.1.0 protocol reference](https://github.com/Palakis/obs-websocket/blob/4.1.0/PROTOCOL.md)
|
||||
- [4.0.0 protocol reference](https://github.com/Palakis/obs-websocket/blob/4.0.0/PROTOCOL.md)
|
||||
# obs-websocket 4.9.0 protocol reference
|
||||
|
||||
# General Introduction
|
||||
Messages are exchanged between the client and the server as JSON objects.
|
||||
This protocol is based on the original OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio.
|
||||
|
||||
This protocol is based on the original OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio. As of v5.0.0, backwards compatability with the protocol will not be kept.
|
||||
|
||||
# Authentication
|
||||
OBSWebSocket uses SHA256 to transmit credentials.
|
||||
**Starting with obs-websocket 4.9, authentication is enabled by default and users are encouraged to configure a password on first run.**
|
||||
|
||||
`obs-websocket` uses SHA256 to transmit credentials.
|
||||
|
||||
A request for [`GetAuthRequired`](#getauthrequired) returns two elements:
|
||||
- A `challenge`: a random string that will be used to generate the auth response.
|
||||
@ -37,3 +34,5 @@ auth_response_string = secret + challenge
|
||||
auth_response_hash = binary_sha256(auth_response_string)
|
||||
auth_response = base64_encode(auth_response_hash)
|
||||
```
|
||||
|
||||
You can also refer to any of the client libraries listed on the [README](README.md) for examples of how to authenticate.
|
||||
|
@ -6,6 +6,6 @@ Requests are sent by the client and require at least the following two fields:
|
||||
Once a request is sent, the server will return a JSON response with at least the following fields:
|
||||
- `message-id` _String_: The client defined identifier specified in the request.
|
||||
- `status` _String_: Response status, will be one of the following: `ok`, `error`
|
||||
- `error` _String_: An error message accompanying an `error` status.
|
||||
- `error` _String (Optional)_: An error message accompanying an `error` status.
|
||||
|
||||
Additional information may be required/returned depending on the request type. See below for more information.
|
||||
|
2
docs/partials/typedefsHeader.md
Normal file
2
docs/partials/typedefsHeader.md
Normal file
@ -0,0 +1,2 @@
|
||||
# Typedefs
|
||||
These are complex types, such as `Source` and `Scene`, which are used as arguments or return values in multiple requests and/or events.
|
@ -7,6 +7,19 @@
|
||||
|
||||
|
||||
|
||||
{{#read "partials/typedefsHeader.md"}}{{/read}}
|
||||
|
||||
{{#each typedefs}}
|
||||
## {{typedefs.0.name}}
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
{{#each properties}}
|
||||
| `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} |
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
|
||||
|
||||
|
||||
{{#read "partials/eventsHeader.md"}}{{/read}}
|
||||
|
||||
{{#each events}}
|
||||
@ -15,6 +28,10 @@
|
||||
{{#each this}}
|
||||
### {{name}}
|
||||
|
||||
{{#if deprecated}}
|
||||
- **⚠️ Deprecated. {{deprecated}} ⚠️**
|
||||
{{/if}}
|
||||
|
||||
{{#eq since "unreleased"}}
|
||||
- Unreleased
|
||||
{{else}}
|
||||
@ -29,7 +46,7 @@
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
{{#each returns}}
|
||||
| `{{name}}` | _{{depipe type}}_ | {{{description}}} |
|
||||
| `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} |
|
||||
{{/each}}
|
||||
|
||||
{{else}}
|
||||
@ -51,6 +68,10 @@ _No additional response items._
|
||||
{{#each this}}
|
||||
### {{name}}
|
||||
|
||||
{{#if deprecated}}
|
||||
- **⚠️ Deprecated. {{deprecated}} ⚠️**
|
||||
{{/if}}
|
||||
|
||||
{{#eq since "unreleased"}}
|
||||
- Unreleased
|
||||
{{else}}
|
||||
@ -65,7 +86,7 @@ _No additional response items._
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
{{#each params}}
|
||||
| `{{name}}` | _{{depipe type}}_ | {{{description}}} |
|
||||
| `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} |
|
||||
{{/each}}
|
||||
|
||||
{{else}}
|
||||
@ -78,7 +99,7 @@ _No specified parameters._
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
{{#each returns}}
|
||||
| `{{name}}` | _{{depipe type}}_ | {{{description}}} |
|
||||
| `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} |
|
||||
{{/each}}
|
||||
|
||||
{{else}}
|
||||
@ -89,4 +110,3 @@ _No additional response items._
|
||||
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
|
||||
|
@ -1,113 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include "obs-websocket.h"
|
||||
#include "Config.h"
|
||||
#include "WSServer.h"
|
||||
#include "settings-dialog.h"
|
||||
#include "ui_settings-dialog.h"
|
||||
|
||||
#define CHANGE_ME "changeme"
|
||||
|
||||
SettingsDialog::SettingsDialog(QWidget* parent) :
|
||||
QDialog(parent, Qt::Dialog),
|
||||
ui(new Ui::SettingsDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->authRequired, &QCheckBox::stateChanged,
|
||||
this, &SettingsDialog::AuthCheckboxChanged);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted,
|
||||
this, &SettingsDialog::FormAccepted);
|
||||
|
||||
|
||||
AuthCheckboxChanged();
|
||||
}
|
||||
|
||||
void SettingsDialog::showEvent(QShowEvent* event)
|
||||
{
|
||||
Config* conf = Config::Current();
|
||||
|
||||
ui->serverEnabled->setChecked(conf->ServerEnabled);
|
||||
ui->serverPort->setValue(conf->ServerPort);
|
||||
|
||||
ui->debugEnabled->setChecked(conf->DebugEnabled);
|
||||
|
||||
ui->authRequired->setChecked(conf->AuthRequired);
|
||||
ui->password->setText(CHANGE_ME);
|
||||
}
|
||||
|
||||
void SettingsDialog::ToggleShowHide()
|
||||
{
|
||||
if (!isVisible())
|
||||
setVisible(true);
|
||||
else
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
void SettingsDialog::AuthCheckboxChanged()
|
||||
{
|
||||
if (ui->authRequired->isChecked())
|
||||
ui->password->setEnabled(true);
|
||||
else
|
||||
ui->password->setEnabled(false);
|
||||
}
|
||||
|
||||
void SettingsDialog::FormAccepted()
|
||||
{
|
||||
Config* conf = Config::Current();
|
||||
|
||||
conf->ServerEnabled = ui->serverEnabled->isChecked();
|
||||
conf->ServerPort = ui->serverPort->value();
|
||||
|
||||
conf->DebugEnabled = ui->debugEnabled->isChecked();
|
||||
|
||||
if (ui->authRequired->isChecked())
|
||||
{
|
||||
if (ui->password->text() != CHANGE_ME)
|
||||
{
|
||||
QByteArray pwd = ui->password->text().toUtf8();
|
||||
const char *new_password = pwd;
|
||||
|
||||
conf->SetPassword(new_password);
|
||||
}
|
||||
|
||||
if (strcmp(Config::Current()->Secret, "") != 0)
|
||||
conf->AuthRequired = true;
|
||||
else
|
||||
conf->AuthRequired = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
conf->AuthRequired = false;
|
||||
}
|
||||
|
||||
conf->Save();
|
||||
|
||||
if (conf->ServerEnabled)
|
||||
WSServer::Instance->Start(conf->ServerPort);
|
||||
else
|
||||
WSServer::Instance->Stop();
|
||||
}
|
||||
|
||||
SettingsDialog::~SettingsDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
@ -2,8 +2,8 @@
|
||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||
|
||||
#define MyAppName "obs-websocket"
|
||||
#define MyAppVersion "4.2.0"
|
||||
#define MyAppPublisher "St<EFBFBD>phane Lepin"
|
||||
#define MyAppVersion "4.9.0"
|
||||
#define MyAppPublisher "Stephane Lepin"
|
||||
#define MyAppURL "http://github.com/Palakis/obs-websocket"
|
||||
|
||||
[Setup]
|
||||
@ -20,17 +20,17 @@ AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
DefaultDirName={code:GetDirName}
|
||||
DefaultGroupName={#MyAppName}
|
||||
OutputBaseFilename=obs-websocket-{#MyAppVersion}-Windows-Installer
|
||||
OutputBaseFilename=obs-websocket-Windows-Installer
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
LicenseFile=..\LICENSE
|
||||
DirExistsWarning=no
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
|
||||
|
||||
[Files]
|
||||
Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "..\LICENSE"; Flags: dontcopy
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
[Icons]
|
||||
@ -38,17 +38,32 @@ Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}"
|
||||
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
|
||||
|
||||
[Code]
|
||||
procedure InitializeWizard();
|
||||
var
|
||||
GPLText: AnsiString;
|
||||
Page: TOutputMsgMemoWizardPage;
|
||||
begin
|
||||
ExtractTemporaryFile('LICENSE');
|
||||
LoadStringFromFile(ExpandConstant('{tmp}\LICENSE'), GPLText);
|
||||
|
||||
Page := CreateOutputMsgMemoPage(wpWelcome,
|
||||
'License Information', 'Please review the license terms before installing obs-websocket',
|
||||
'Press Page Down to see the rest of the agreement. Once you are aware of your rights, click Next to continue.',
|
||||
String(GPLText)
|
||||
);
|
||||
end;
|
||||
|
||||
// credit where it's due :
|
||||
// following function come from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45
|
||||
function GetDirName(Value: string): string;
|
||||
var
|
||||
InstallPath: string;
|
||||
InstallPath: string;
|
||||
begin
|
||||
// initialize default path, which will be returned when the following registry
|
||||
// key queries fail due to missing keys or for some different reason
|
||||
Result := '{pf}\obs-studio';
|
||||
// query the first registry value; if this succeeds, return the obtained value
|
||||
if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then
|
||||
Result := InstallPath
|
||||
// initialize default path, which will be returned when the following registry
|
||||
// key queries fail due to missing keys or for some different reason
|
||||
Result := '{pf}\obs-studio';
|
||||
// query the first registry value; if this succeeds, return the obtained value
|
||||
if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then
|
||||
Result := InstallPath
|
||||
end;
|
||||
|
||||
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <obs-module.h>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QAction>
|
||||
#include <QMainWindow>
|
||||
#include <QTimer>
|
||||
|
||||
#include "obs-websocket.h"
|
||||
#include "WSServer.h"
|
||||
#include "WSEvents.h"
|
||||
#include "Config.h"
|
||||
#include "forms/settings-dialog.h"
|
||||
|
||||
OBS_DECLARE_MODULE()
|
||||
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
|
||||
|
||||
SettingsDialog* settings_dialog;
|
||||
|
||||
bool obs_module_load(void) {
|
||||
blog(LOG_INFO, "you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
|
||||
blog(LOG_INFO, "qt version (compile-time): %s ; qt version (run-time): %s",
|
||||
QT_VERSION_STR, qVersion());
|
||||
|
||||
// Core setup
|
||||
Config* config = Config::Current();
|
||||
config->Load();
|
||||
|
||||
WSServer::Instance = new WSServer();
|
||||
WSEvents::Instance = new WSEvents(WSServer::Instance);
|
||||
|
||||
if (config->ServerEnabled)
|
||||
WSServer::Instance->Start(config->ServerPort);
|
||||
|
||||
// UI setup
|
||||
QAction* menu_action = (QAction*)obs_frontend_add_tools_menu_qaction(
|
||||
obs_module_text("OBSWebsocket.Menu.SettingsItem"));
|
||||
|
||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||
QMainWindow* main_window = (QMainWindow*)obs_frontend_get_main_window();
|
||||
settings_dialog = new SettingsDialog(main_window);
|
||||
obs_frontend_pop_ui_translation();
|
||||
|
||||
auto menu_cb = [] {
|
||||
settings_dialog->ToggleShowHide();
|
||||
};
|
||||
menu_action->connect(menu_action, &QAction::triggered, menu_cb);
|
||||
|
||||
// Loading finished
|
||||
blog(LOG_INFO, "module loaded!");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void obs_module_unload() {
|
||||
blog(LOG_INFO, "goodbye!");
|
||||
}
|
||||
|
348
src/Config.cpp
Normal file
348
src/Config.cpp
Normal file
@ -0,0 +1,348 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QCryptographicHash>
|
||||
#include <QtCore/QTime>
|
||||
#include <QtWidgets/QSystemTrayIcon>
|
||||
#include <QtWidgets/QMainWindow>
|
||||
#include <QtWidgets/QInputDialog>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
#define SECTION_NAME "WebsocketAPI"
|
||||
#define PARAM_ENABLE "ServerEnabled"
|
||||
#define PARAM_PORT "ServerPort"
|
||||
#define PARAM_LOCKTOIPV4 "LockToIPv4"
|
||||
#define PARAM_DEBUG "DebugEnabled"
|
||||
#define PARAM_ALERT "AlertsEnabled"
|
||||
#define PARAM_AUTHREQUIRED "AuthRequired"
|
||||
#define PARAM_SECRET "AuthSecret"
|
||||
#define PARAM_SALT "AuthSalt"
|
||||
|
||||
#define GLOBAL_AUTH_SETUP_PROMPTED "AuthSetupPrompted"
|
||||
|
||||
#include "Utils.h"
|
||||
#include "WSServer.h"
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
#define QT_TO_UTF8(str) str.toUtf8().constData()
|
||||
|
||||
Config::Config() :
|
||||
ServerEnabled(true),
|
||||
ServerPort(4444),
|
||||
LockToIPv4(false),
|
||||
DebugEnabled(false),
|
||||
AlertsEnabled(true),
|
||||
AuthRequired(true),
|
||||
Secret(""),
|
||||
Salt(""),
|
||||
SettingsLoaded(false)
|
||||
{
|
||||
qsrand(QTime::currentTime().msec());
|
||||
|
||||
SetDefaults();
|
||||
SessionChallenge = GenerateSalt();
|
||||
|
||||
obs_frontend_add_event_callback(OnFrontendEvent, this);
|
||||
}
|
||||
|
||||
Config::~Config()
|
||||
{
|
||||
obs_frontend_remove_event_callback(OnFrontendEvent, this);
|
||||
}
|
||||
|
||||
void Config::Load()
|
||||
{
|
||||
config_t* obsConfig = GetConfigStore();
|
||||
|
||||
ServerEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ENABLE);
|
||||
ServerPort = config_get_uint(obsConfig, SECTION_NAME, PARAM_PORT);
|
||||
LockToIPv4 = config_get_bool(obsConfig, SECTION_NAME, PARAM_LOCKTOIPV4);
|
||||
|
||||
DebugEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_DEBUG);
|
||||
AlertsEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ALERT);
|
||||
|
||||
AuthRequired = config_get_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED);
|
||||
Secret = config_get_string(obsConfig, SECTION_NAME, PARAM_SECRET);
|
||||
Salt = config_get_string(obsConfig, SECTION_NAME, PARAM_SALT);
|
||||
}
|
||||
|
||||
void Config::Save()
|
||||
{
|
||||
config_t* obsConfig = GetConfigStore();
|
||||
|
||||
config_set_bool(obsConfig, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||
config_set_uint(obsConfig, SECTION_NAME, PARAM_PORT, ServerPort);
|
||||
config_set_bool(obsConfig, SECTION_NAME, PARAM_LOCKTOIPV4, LockToIPv4);
|
||||
|
||||
config_set_bool(obsConfig, SECTION_NAME, PARAM_DEBUG, DebugEnabled);
|
||||
config_set_bool(obsConfig, SECTION_NAME, PARAM_ALERT, AlertsEnabled);
|
||||
|
||||
config_set_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||
config_set_string(obsConfig, SECTION_NAME, PARAM_SECRET,
|
||||
QT_TO_UTF8(Secret));
|
||||
config_set_string(obsConfig, SECTION_NAME, PARAM_SALT,
|
||||
QT_TO_UTF8(Salt));
|
||||
|
||||
config_save(obsConfig);
|
||||
}
|
||||
|
||||
void Config::SetDefaults()
|
||||
{
|
||||
// OBS Config defaults
|
||||
config_t* obsConfig = GetConfigStore();
|
||||
if (obsConfig) {
|
||||
config_set_default_bool(obsConfig,
|
||||
SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||
config_set_default_uint(obsConfig,
|
||||
SECTION_NAME, PARAM_PORT, ServerPort);
|
||||
config_set_default_bool(obsConfig,
|
||||
SECTION_NAME, PARAM_LOCKTOIPV4, LockToIPv4);
|
||||
|
||||
config_set_default_bool(obsConfig,
|
||||
SECTION_NAME, PARAM_DEBUG, DebugEnabled);
|
||||
config_set_default_bool(obsConfig,
|
||||
SECTION_NAME, PARAM_ALERT, AlertsEnabled);
|
||||
|
||||
config_set_default_bool(obsConfig,
|
||||
SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||
config_set_default_string(obsConfig,
|
||||
SECTION_NAME, PARAM_SECRET, QT_TO_UTF8(Secret));
|
||||
config_set_default_string(obsConfig,
|
||||
SECTION_NAME, PARAM_SALT, QT_TO_UTF8(Salt));
|
||||
}
|
||||
}
|
||||
|
||||
config_t* Config::GetConfigStore()
|
||||
{
|
||||
return obs_frontend_get_profile_config();
|
||||
}
|
||||
|
||||
void Config::MigrateFromGlobalSettings()
|
||||
{
|
||||
config_t* source = obs_frontend_get_global_config();
|
||||
config_t* destination = obs_frontend_get_profile_config();
|
||||
|
||||
if(config_has_user_value(source, SECTION_NAME, PARAM_ENABLE)) {
|
||||
bool value = config_get_bool(source, SECTION_NAME, PARAM_ENABLE);
|
||||
config_set_bool(destination, SECTION_NAME, PARAM_ENABLE, value);
|
||||
|
||||
config_remove_value(source, SECTION_NAME, PARAM_ENABLE);
|
||||
}
|
||||
|
||||
if(config_has_user_value(source, SECTION_NAME, PARAM_PORT)) {
|
||||
uint64_t value = config_get_uint(source, SECTION_NAME, PARAM_PORT);
|
||||
config_set_uint(destination, SECTION_NAME, PARAM_PORT, value);
|
||||
|
||||
config_remove_value(source, SECTION_NAME, PARAM_PORT);
|
||||
}
|
||||
|
||||
if(config_has_user_value(source, SECTION_NAME, PARAM_LOCKTOIPV4)) {
|
||||
bool value = config_get_bool(source, SECTION_NAME, PARAM_LOCKTOIPV4);
|
||||
config_set_bool(destination, SECTION_NAME, PARAM_LOCKTOIPV4, value);
|
||||
|
||||
config_remove_value(source, SECTION_NAME, PARAM_LOCKTOIPV4);
|
||||
}
|
||||
|
||||
if(config_has_user_value(source, SECTION_NAME, PARAM_DEBUG)) {
|
||||
bool value = config_get_bool(source, SECTION_NAME, PARAM_DEBUG);
|
||||
config_set_bool(destination, SECTION_NAME, PARAM_DEBUG, value);
|
||||
|
||||
config_remove_value(source, SECTION_NAME, PARAM_DEBUG);
|
||||
}
|
||||
|
||||
if(config_has_user_value(source, SECTION_NAME, PARAM_ALERT)) {
|
||||
bool value = config_get_bool(source, SECTION_NAME, PARAM_ALERT);
|
||||
config_set_bool(destination, SECTION_NAME, PARAM_ALERT, value);
|
||||
|
||||
config_remove_value(source, SECTION_NAME, PARAM_ALERT);
|
||||
}
|
||||
|
||||
if(config_has_user_value(source, SECTION_NAME, PARAM_AUTHREQUIRED)) {
|
||||
bool value = config_get_bool(source, SECTION_NAME, PARAM_AUTHREQUIRED);
|
||||
config_set_bool(destination, SECTION_NAME, PARAM_AUTHREQUIRED, value);
|
||||
|
||||
config_remove_value(source, SECTION_NAME, PARAM_AUTHREQUIRED);
|
||||
}
|
||||
|
||||
if(config_has_user_value(source, SECTION_NAME, PARAM_SECRET)) {
|
||||
const char* value = config_get_string(source, SECTION_NAME, PARAM_SECRET);
|
||||
config_set_string(destination, SECTION_NAME, PARAM_SECRET, value);
|
||||
|
||||
config_remove_value(source, SECTION_NAME, PARAM_SECRET);
|
||||
}
|
||||
|
||||
if(config_has_user_value(source, SECTION_NAME, PARAM_SALT)) {
|
||||
const char* value = config_get_string(source, SECTION_NAME, PARAM_SALT);
|
||||
config_set_string(destination, SECTION_NAME, PARAM_SALT, value);
|
||||
|
||||
config_remove_value(source, SECTION_NAME, PARAM_SALT);
|
||||
}
|
||||
|
||||
config_save(destination);
|
||||
}
|
||||
|
||||
QString Config::GenerateSalt()
|
||||
{
|
||||
// Generate 32 random chars
|
||||
const size_t randomCount = 32;
|
||||
QByteArray randomChars;
|
||||
for (size_t i = 0; i < randomCount; i++) {
|
||||
randomChars.append((char)qrand());
|
||||
}
|
||||
|
||||
// Convert the 32 random chars to a base64 string
|
||||
QString salt = randomChars.toBase64();
|
||||
|
||||
return salt;
|
||||
}
|
||||
|
||||
QString Config::GenerateSecret(QString password, QString salt)
|
||||
{
|
||||
// Concatenate the password and the salt
|
||||
QString passAndSalt = "";
|
||||
passAndSalt += password;
|
||||
passAndSalt += salt;
|
||||
|
||||
// Generate a SHA256 hash of the password and salt
|
||||
auto challengeHash = QCryptographicHash::hash(
|
||||
passAndSalt.toUtf8(),
|
||||
QCryptographicHash::Algorithm::Sha256
|
||||
);
|
||||
|
||||
// Encode SHA256 hash to Base64
|
||||
QString challenge = challengeHash.toBase64();
|
||||
|
||||
return challenge;
|
||||
}
|
||||
|
||||
void Config::SetPassword(QString password)
|
||||
{
|
||||
QString newSalt = GenerateSalt();
|
||||
QString newChallenge = GenerateSecret(password, newSalt);
|
||||
|
||||
this->Salt = newSalt;
|
||||
this->Secret = newChallenge;
|
||||
}
|
||||
|
||||
bool Config::CheckAuth(QString response)
|
||||
{
|
||||
// Concatenate auth secret with the challenge sent to the user
|
||||
QString challengeAndResponse = "";
|
||||
challengeAndResponse += Secret;
|
||||
challengeAndResponse += SessionChallenge;
|
||||
|
||||
// Generate a SHA256 hash of challengeAndResponse
|
||||
auto hash = QCryptographicHash::hash(
|
||||
challengeAndResponse.toUtf8(),
|
||||
QCryptographicHash::Algorithm::Sha256
|
||||
);
|
||||
|
||||
// Encode the SHA256 hash to Base64
|
||||
QString expectedResponse = hash.toBase64();
|
||||
|
||||
bool authSuccess = false;
|
||||
if (response == expectedResponse) {
|
||||
SessionChallenge = GenerateSalt();
|
||||
authSuccess = true;
|
||||
}
|
||||
|
||||
return authSuccess;
|
||||
}
|
||||
|
||||
void Config::OnFrontendEvent(enum obs_frontend_event event, void* param)
|
||||
{
|
||||
auto config = reinterpret_cast<Config*>(param);
|
||||
|
||||
if (event == OBS_FRONTEND_EVENT_PROFILE_CHANGED) {
|
||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||
QString startMessage = QObject::tr("OBSWebsocket.ProfileChanged.Started");
|
||||
QString stopMessage = QObject::tr("OBSWebsocket.ProfileChanged.Stopped");
|
||||
QString restartMessage = QObject::tr("OBSWebsocket.ProfileChanged.Restarted");
|
||||
obs_frontend_pop_ui_translation();
|
||||
|
||||
bool previousEnabled = config->ServerEnabled;
|
||||
uint64_t previousPort = config->ServerPort;
|
||||
bool previousLock = config->LockToIPv4;
|
||||
|
||||
config->SetDefaults();
|
||||
config->Load();
|
||||
|
||||
if (config->ServerEnabled != previousEnabled || config->ServerPort != previousPort || config->LockToIPv4 != previousLock) {
|
||||
auto server = GetServer();
|
||||
server->stop();
|
||||
|
||||
if (config->ServerEnabled) {
|
||||
server->start(config->ServerPort, config->LockToIPv4);
|
||||
|
||||
if (previousEnabled != config->ServerEnabled) {
|
||||
Utils::SysTrayNotify(startMessage, QSystemTrayIcon::MessageIcon::Information);
|
||||
} else {
|
||||
Utils::SysTrayNotify(restartMessage, QSystemTrayIcon::MessageIcon::Information);
|
||||
}
|
||||
} else {
|
||||
Utils::SysTrayNotify(stopMessage, QSystemTrayIcon::MessageIcon::Information);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) {
|
||||
FirstRunPasswordSetup();
|
||||
}
|
||||
}
|
||||
|
||||
void Config::FirstRunPasswordSetup()
|
||||
{
|
||||
// check if we already showed the auth setup prompt to the user, independently of the current settings (tied to the current profile)
|
||||
config_t* globalConfig = obs_frontend_get_global_config();
|
||||
bool alreadyPrompted = config_get_bool(globalConfig, SECTION_NAME, GLOBAL_AUTH_SETUP_PROMPTED);
|
||||
if (alreadyPrompted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// lift the flag up and save it
|
||||
config_set_bool(globalConfig, SECTION_NAME, GLOBAL_AUTH_SETUP_PROMPTED, true);
|
||||
config_save(globalConfig);
|
||||
|
||||
// check if the password is already set
|
||||
auto config = GetConfig();
|
||||
if (!(config->Secret.isEmpty()) && !(config->Salt.isEmpty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||
QString dialogTitle = QObject::tr("OBSWebsocket.InitialPasswordSetup.Title");
|
||||
QString dialogText = QObject::tr("OBSWebsocket.InitialPasswordSetup.Text");
|
||||
QString dismissedText = QObject::tr("OBSWebsocket.InitialPasswordSetup.DismissedText");
|
||||
obs_frontend_pop_ui_translation();
|
||||
|
||||
auto mainWindow = reinterpret_cast<QMainWindow*>(
|
||||
obs_frontend_get_main_window()
|
||||
);
|
||||
|
||||
QMessageBox::StandardButton response = QMessageBox::question(mainWindow, dialogTitle, dialogText);
|
||||
if (response == QMessageBox::Yes) {
|
||||
ShowPasswordSetting();
|
||||
}
|
||||
else {
|
||||
// tell the user they still can set the password later in our settings dialog
|
||||
QMessageBox::information(mainWindow, dialogTitle, dismissedText);
|
||||
}
|
||||
}
|
59
src/Config.h
Normal file
59
src/Config.h
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <util/config-file.h>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QSharedPointer>
|
||||
|
||||
class Config {
|
||||
public:
|
||||
Config();
|
||||
~Config();
|
||||
void Load();
|
||||
void Save();
|
||||
void SetDefaults();
|
||||
config_t* GetConfigStore();
|
||||
|
||||
void MigrateFromGlobalSettings();
|
||||
|
||||
void SetPassword(QString password);
|
||||
bool CheckAuth(QString userChallenge);
|
||||
QString GenerateSalt();
|
||||
static QString GenerateSecret(
|
||||
QString password, QString salt);
|
||||
|
||||
bool ServerEnabled;
|
||||
uint64_t ServerPort;
|
||||
bool LockToIPv4;
|
||||
|
||||
bool DebugEnabled;
|
||||
bool AlertsEnabled;
|
||||
|
||||
bool AuthRequired;
|
||||
QString Secret;
|
||||
QString Salt;
|
||||
QString SessionChallenge;
|
||||
bool SettingsLoaded;
|
||||
|
||||
private:
|
||||
static void OnFrontendEvent(enum obs_frontend_event event, void* param);
|
||||
static void FirstRunPasswordSetup();
|
||||
};
|
34
src/ConnectionProperties.cpp
Normal file
34
src/ConnectionProperties.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "ConnectionProperties.h"
|
||||
|
||||
ConnectionProperties::ConnectionProperties()
|
||||
: _authenticated(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool ConnectionProperties::isAuthenticated()
|
||||
{
|
||||
return _authenticated.load();
|
||||
}
|
||||
|
||||
void ConnectionProperties::setAuthenticated(bool authenticated)
|
||||
{
|
||||
_authenticated.store(authenticated);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -16,12 +16,16 @@ You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef OBSWEBSOCKET_H
|
||||
#define OBSWEBSOCKET_H
|
||||
#pragma once
|
||||
|
||||
#define PROP_AUTHENTICATED "wsclient_authenticated"
|
||||
#define OBS_WEBSOCKET_VERSION "4.2.0"
|
||||
#include <atomic>
|
||||
|
||||
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
|
||||
|
||||
#endif // OBSWEBSOCKET_H
|
||||
class ConnectionProperties
|
||||
{
|
||||
public:
|
||||
explicit ConnectionProperties();
|
||||
bool isAuthenticated();
|
||||
void setAuthenticated(bool authenticated);
|
||||
private:
|
||||
std::atomic<bool> _authenticated;
|
||||
};
|
943
src/Utils.cpp
Normal file
943
src/Utils.cpp
Normal file
@ -0,0 +1,943 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <QtWidgets/QMainWindow>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <obs.hpp>
|
||||
#include <util/platform.h>
|
||||
#include <obs-data.h>
|
||||
|
||||
#include "obs-websocket.h"
|
||||
|
||||
#include "Utils.h"
|
||||
#include "Config.h"
|
||||
|
||||
Q_DECLARE_METATYPE(OBSScene);
|
||||
|
||||
const QHash<obs_bounds_type, QString> boundTypeNames = {
|
||||
{ OBS_BOUNDS_STRETCH, "OBS_BOUNDS_STRETCH" },
|
||||
{ OBS_BOUNDS_SCALE_INNER, "OBS_BOUNDS_SCALE_INNER" },
|
||||
{ OBS_BOUNDS_SCALE_OUTER, "OBS_BOUNDS_SCALE_OUTER" },
|
||||
{ OBS_BOUNDS_SCALE_TO_WIDTH, "OBS_BOUNDS_SCALE_TO_WIDTH" },
|
||||
{ OBS_BOUNDS_SCALE_TO_HEIGHT, "OBS_BOUNDS_SCALE_TO_HEIGHT" },
|
||||
{ OBS_BOUNDS_MAX_ONLY, "OBS_BOUNDS_MAX_ONLY" },
|
||||
{ OBS_BOUNDS_NONE, "OBS_BOUNDS_NONE" },
|
||||
};
|
||||
|
||||
QString getBoundsNameFromType(obs_bounds_type type) {
|
||||
QString fallback = boundTypeNames.value(OBS_BOUNDS_NONE);
|
||||
return boundTypeNames.value(type, fallback);
|
||||
}
|
||||
|
||||
obs_bounds_type getBoundsTypeFromName(QString name) {
|
||||
return boundTypeNames.key(name);
|
||||
}
|
||||
|
||||
bool Utils::StringInStringList(char** strings, const char* string) {
|
||||
if (!strings) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t index = 0;
|
||||
while (strings[index] != NULL) {
|
||||
char* value = strings[index];
|
||||
|
||||
if (strcmp(value, string) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::StringListToArray(char** strings, const char* key) {
|
||||
obs_data_array_t* list = obs_data_array_create();
|
||||
|
||||
if (!strings || !key) {
|
||||
return list; // empty list
|
||||
}
|
||||
|
||||
size_t index = 0;
|
||||
char* value = nullptr;
|
||||
|
||||
do {
|
||||
value = strings[index];
|
||||
|
||||
OBSDataAutoRelease item = obs_data_create();
|
||||
obs_data_set_string(item, key, value);
|
||||
|
||||
if (value) {
|
||||
obs_data_array_push_back(list, item);
|
||||
}
|
||||
|
||||
index++;
|
||||
} while (value != nullptr);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetSceneItems(obs_source_t* source) {
|
||||
obs_data_array_t* items = obs_data_array_create();
|
||||
OBSScene scene = obs_scene_from_source(source);
|
||||
|
||||
if (!scene) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
obs_scene_enum_items(scene, [](
|
||||
obs_scene_t* scene,
|
||||
obs_sceneitem_t* currentItem,
|
||||
void* param)
|
||||
{
|
||||
obs_data_array_t* data = reinterpret_cast<obs_data_array_t*>(param);
|
||||
|
||||
OBSDataAutoRelease itemData = GetSceneItemData(currentItem);
|
||||
obs_data_array_insert(data, 0, itemData);
|
||||
return true;
|
||||
}, items);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} `SceneItem` An OBS Scene Item.
|
||||
* @property {Number} `cy`
|
||||
* @property {Number} `cx`
|
||||
* @property {Number} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.
|
||||
* @property {String} `name` The name of this Scene Item.
|
||||
* @property {int} `id` Scene item ID
|
||||
* @property {Boolean} `render` Whether or not this Scene Item is set to "visible".
|
||||
* @property {Boolean} `muted` Whether or not this Scene Item is muted.
|
||||
* @property {Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around
|
||||
* @property {Number} `source_cx`
|
||||
* @property {Number} `source_cy`
|
||||
* @property {String} `type` Source type. Value is one of the following: "input", "filter", "transition", "scene" or "unknown"
|
||||
* @property {Number} `volume`
|
||||
* @property {Number} `x`
|
||||
* @property {Number} `y`
|
||||
* @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
|
||||
* @property {Array<SceneItem> (optional)} `groupChildren` List of children (if this item is a group)
|
||||
*/
|
||||
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
|
||||
if (!item) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
vec2 pos;
|
||||
obs_sceneitem_get_pos(item, &pos);
|
||||
|
||||
vec2 scale;
|
||||
obs_sceneitem_get_scale(item, &scale);
|
||||
|
||||
// obs_sceneitem_get_source doesn't increase the refcount
|
||||
OBSSource itemSource = obs_sceneitem_get_source(item);
|
||||
float item_width = float(obs_source_get_width(itemSource));
|
||||
float item_height = float(obs_source_get_height(itemSource));
|
||||
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_string(data, "name",
|
||||
obs_source_get_name(itemSource));
|
||||
obs_data_set_int(data, "id",
|
||||
obs_sceneitem_get_id(item));
|
||||
obs_data_set_string(data, "type",
|
||||
obs_source_get_id(itemSource));
|
||||
obs_data_set_double(data, "volume",
|
||||
obs_source_get_volume(itemSource));
|
||||
obs_data_set_double(data, "x", pos.x);
|
||||
obs_data_set_double(data, "y", pos.y);
|
||||
obs_data_set_int(data, "source_cx", (int)item_width);
|
||||
obs_data_set_int(data, "source_cy", (int)item_height);
|
||||
obs_data_set_bool(data, "muted", obs_source_muted(itemSource));
|
||||
obs_data_set_int(data, "alignment", (int)obs_sceneitem_get_alignment(item));
|
||||
obs_data_set_double(data, "cx", item_width * scale.x);
|
||||
obs_data_set_double(data, "cy", item_height * scale.y);
|
||||
obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
|
||||
obs_data_set_bool(data, "locked", obs_sceneitem_locked(item));
|
||||
|
||||
obs_scene_t* parent = obs_sceneitem_get_scene(item);
|
||||
if (parent) {
|
||||
OBSSource parentSource = obs_scene_get_source(parent);
|
||||
QString parentKind = obs_source_get_id(parentSource);
|
||||
if (parentKind == "group") {
|
||||
obs_data_set_string(data, "parentGroupName", obs_source_get_name(parentSource));
|
||||
}
|
||||
}
|
||||
|
||||
if (obs_sceneitem_is_group(item)) {
|
||||
OBSDataArrayAutoRelease children = obs_data_array_create();
|
||||
obs_sceneitem_group_enum_items(item, [](obs_scene_t*, obs_sceneitem_t* currentItem, void* param) {
|
||||
obs_data_array_t* items = reinterpret_cast<obs_data_array_t*>(param);
|
||||
|
||||
OBSDataAutoRelease itemData = GetSceneItemData(currentItem);
|
||||
obs_data_array_push_back(items, itemData);
|
||||
|
||||
return true;
|
||||
}, children);
|
||||
obs_data_set_array(data, "groupChildren", children);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_scene_t* scene, QString name) {
|
||||
if (!scene) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct current_search {
|
||||
QString query;
|
||||
obs_sceneitem_t* result;
|
||||
bool (*enumCallback)(obs_scene_t*, obs_sceneitem_t*, void*);
|
||||
};
|
||||
|
||||
current_search search;
|
||||
search.query = name;
|
||||
search.result = nullptr;
|
||||
|
||||
search.enumCallback = [](
|
||||
obs_scene_t* scene,
|
||||
obs_sceneitem_t* currentItem,
|
||||
void* param)
|
||||
{
|
||||
current_search* search = reinterpret_cast<current_search*>(param);
|
||||
|
||||
if (obs_sceneitem_is_group(currentItem)) {
|
||||
obs_sceneitem_group_enum_items(currentItem, search->enumCallback, search);
|
||||
if (search->result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QString currentItemName =
|
||||
obs_source_get_name(obs_sceneitem_get_source(currentItem));
|
||||
|
||||
if (currentItemName == search->query) {
|
||||
search->result = currentItem;
|
||||
obs_sceneitem_addref(search->result);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
obs_scene_enum_items(scene, search.enumCallback, &search);
|
||||
|
||||
return search.result;
|
||||
}
|
||||
|
||||
obs_sceneitem_t* Utils::GetSceneItemFromId(obs_scene_t* scene, int64_t id) {
|
||||
if (!scene) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct current_search {
|
||||
int query;
|
||||
obs_sceneitem_t* result;
|
||||
bool (*enumCallback)(obs_scene_t*, obs_sceneitem_t*, void*);
|
||||
};
|
||||
|
||||
current_search search;
|
||||
search.query = id;
|
||||
search.result = nullptr;
|
||||
|
||||
search.enumCallback = [](
|
||||
obs_scene_t* scene,
|
||||
obs_sceneitem_t* currentItem,
|
||||
void* param)
|
||||
{
|
||||
current_search* search = reinterpret_cast<current_search*>(param);
|
||||
|
||||
if (obs_sceneitem_is_group(currentItem)) {
|
||||
obs_sceneitem_group_enum_items(currentItem, search->enumCallback, search);
|
||||
if (search->result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (obs_sceneitem_get_id(currentItem) == search->query) {
|
||||
search->result = currentItem;
|
||||
obs_sceneitem_addref(search->result);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
obs_scene_enum_items(scene, search.enumCallback, &search);
|
||||
|
||||
return search.result;
|
||||
}
|
||||
|
||||
obs_sceneitem_t* Utils::GetSceneItemFromItem(obs_scene_t* scene, obs_data_t* itemInfo) {
|
||||
if (!scene) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OBSDataItemAutoRelease idInfoItem = obs_data_item_byname(itemInfo, "id");
|
||||
int id = obs_data_item_get_int(idInfoItem);
|
||||
|
||||
OBSDataItemAutoRelease nameInfoItem = obs_data_item_byname(itemInfo, "name");
|
||||
const char* name = obs_data_item_get_string(nameInfoItem);
|
||||
|
||||
if (idInfoItem) {
|
||||
obs_sceneitem_t* sceneItem = GetSceneItemFromId(scene, id);
|
||||
obs_source_t* sceneItemSource = obs_sceneitem_get_source(sceneItem);
|
||||
|
||||
QString sceneItemName = obs_source_get_name(sceneItemSource);
|
||||
if (nameInfoItem && (QString(name) != sceneItemName)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return sceneItem;
|
||||
} else if (nameInfoItem) {
|
||||
return GetSceneItemFromName(scene, name);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
obs_sceneitem_t* Utils::GetSceneItemFromRequestField(obs_scene_t* scene, obs_data_item_t* dataItem)
|
||||
{
|
||||
enum obs_data_type dataType = obs_data_item_gettype(dataItem);
|
||||
|
||||
if (dataType == OBS_DATA_OBJECT) {
|
||||
OBSDataAutoRelease itemData = obs_data_item_get_obj(dataItem);
|
||||
return GetSceneItemFromItem(scene, itemData);
|
||||
} else if (dataType == OBS_DATA_STRING) {
|
||||
QString name = obs_data_item_get_string(dataItem);
|
||||
return GetSceneItemFromName(scene, name);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Utils::IsValidAlignment(const uint32_t alignment) {
|
||||
switch (alignment) {
|
||||
case OBS_ALIGN_CENTER:
|
||||
case OBS_ALIGN_LEFT:
|
||||
case OBS_ALIGN_RIGHT:
|
||||
case OBS_ALIGN_TOP:
|
||||
case OBS_ALIGN_BOTTOM:
|
||||
case OBS_ALIGN_TOP | OBS_ALIGN_LEFT:
|
||||
case OBS_ALIGN_TOP | OBS_ALIGN_RIGHT:
|
||||
case OBS_ALIGN_BOTTOM | OBS_ALIGN_LEFT:
|
||||
case OBS_ALIGN_BOTTOM | OBS_ALIGN_RIGHT: {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
obs_source_t* Utils::GetTransitionFromName(QString searchName) {
|
||||
obs_source_t* foundTransition = nullptr;
|
||||
|
||||
obs_frontend_source_list transition_list = {};
|
||||
obs_frontend_get_transitions(&transition_list);
|
||||
|
||||
for (size_t i = 0; i < transition_list.sources.num; i++) {
|
||||
obs_source_t* transition = transition_list.sources.array[i];
|
||||
QString transitionName = obs_source_get_name(transition);
|
||||
|
||||
if (transitionName == searchName) {
|
||||
foundTransition = transition;
|
||||
obs_source_addref(foundTransition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
obs_frontend_source_list_free(&transition_list);
|
||||
return foundTransition;
|
||||
}
|
||||
|
||||
obs_scene_t* Utils::GetSceneFromNameOrCurrent(QString sceneName) {
|
||||
// Both obs_frontend_get_current_scene() and obs_get_source_by_name()
|
||||
// increase the returned source's refcount
|
||||
OBSSourceAutoRelease sceneSource = nullptr;
|
||||
|
||||
if (sceneName.isEmpty() || sceneName.isNull()) {
|
||||
sceneSource = obs_frontend_get_current_scene();
|
||||
}
|
||||
else {
|
||||
sceneSource = obs_get_source_by_name(sceneName.toUtf8());
|
||||
}
|
||||
|
||||
return obs_scene_from_source(sceneSource);
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetScenes() {
|
||||
obs_frontend_source_list sceneList = {};
|
||||
obs_frontend_get_scenes(&sceneList);
|
||||
|
||||
obs_data_array_t* scenes = obs_data_array_create();
|
||||
for (size_t i = 0; i < sceneList.sources.num; i++) {
|
||||
obs_source_t* scene = sceneList.sources.array[i];
|
||||
OBSDataAutoRelease sceneData = GetSceneData(scene);
|
||||
obs_data_array_push_back(scenes, sceneData);
|
||||
}
|
||||
|
||||
obs_frontend_source_list_free(&sceneList);
|
||||
return scenes;
|
||||
}
|
||||
|
||||
obs_data_t* Utils::GetSceneData(obs_source_t* source) {
|
||||
OBSDataArrayAutoRelease sceneItems = GetSceneItems(source);
|
||||
|
||||
obs_data_t* sceneData = obs_data_create();
|
||||
obs_data_set_string(sceneData, "name", obs_source_get_name(source));
|
||||
obs_data_set_array(sceneData, "sources", sceneItems);
|
||||
|
||||
return sceneData;
|
||||
}
|
||||
|
||||
QSpinBox* Utils::GetTransitionDurationControl() {
|
||||
QMainWindow* window = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return window->findChild<QSpinBox*>("transitionDuration");
|
||||
}
|
||||
|
||||
int Utils::GetTransitionDuration(obs_source_t* transition) {
|
||||
if (!transition || obs_source_get_type(transition) != OBS_SOURCE_TYPE_TRANSITION) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString transitionKind = obs_source_get_id(transition);
|
||||
if (transitionKind == "cut_transition") {
|
||||
// If this is a Cut transition, return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (obs_transition_fixed(transition)) {
|
||||
// If this transition has a fixed duration (such as a Stinger),
|
||||
// we don't currently have a way of retrieving that number.
|
||||
// For now, return -1 to indicate that we don't know the actual duration.
|
||||
return -1;
|
||||
}
|
||||
|
||||
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
|
||||
OBSDataAutoRelease destinationSettings = obs_source_get_private_settings(destinationScene);
|
||||
|
||||
// Detect if transition is the global transition or a transition override.
|
||||
// Fetching the duration is different depending on the case.
|
||||
obs_data_item_t* transitionDurationItem = obs_data_item_byname(destinationSettings, "transition_duration");
|
||||
int duration = (
|
||||
transitionDurationItem
|
||||
? obs_data_item_get_int(transitionDurationItem)
|
||||
: obs_frontend_get_transition_duration()
|
||||
);
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
bool Utils::SetTransitionByName(QString transitionName) {
|
||||
OBSSourceAutoRelease transition = GetTransitionFromName(transitionName);
|
||||
|
||||
if (transition) {
|
||||
obs_frontend_set_current_transition(transition);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
obs_data_t* Utils::GetTransitionData(obs_source_t* transition) {
|
||||
int duration = Utils::GetTransitionDuration(transition);
|
||||
if (duration < 0) {
|
||||
blog(LOG_WARNING, "GetTransitionData: duration is negative !");
|
||||
}
|
||||
|
||||
OBSSourceAutoRelease sourceScene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_A);
|
||||
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
|
||||
|
||||
obs_data_t* transitionData = obs_data_create();
|
||||
obs_data_set_string(transitionData, "name", obs_source_get_name(transition));
|
||||
obs_data_set_string(transitionData, "type", obs_source_get_id(transition));
|
||||
obs_data_set_int(transitionData, "duration", duration);
|
||||
|
||||
// When a transition starts and while it is running, SOURCE_A is the source scene
|
||||
// and SOURCE_B is the destination scene.
|
||||
// Before the transition_end event is triggered on a transition, the destination scene
|
||||
// goes into SOURCE_A and SOURCE_B becomes null. This means that, in transition_stop
|
||||
// we don't know what was the source scene
|
||||
// TODO fix this in libobs
|
||||
|
||||
bool isTransitionEndEvent = (sourceScene == destinationScene);
|
||||
if (!isTransitionEndEvent) {
|
||||
obs_data_set_string(transitionData, "from-scene", obs_source_get_name(sourceScene));
|
||||
}
|
||||
|
||||
obs_data_set_string(transitionData, "to-scene", obs_source_get_name(destinationScene));
|
||||
|
||||
return transitionData;
|
||||
}
|
||||
|
||||
QString Utils::OBSVersionString() {
|
||||
uint32_t version = obs_get_version();
|
||||
|
||||
uint8_t major, minor, patch;
|
||||
major = (version >> 24) & 0xFF;
|
||||
minor = (version >> 16) & 0xFF;
|
||||
patch = version & 0xFF;
|
||||
|
||||
QString result = QString("%1.%2.%3")
|
||||
.arg(major).arg(minor).arg(patch);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QSystemTrayIcon* Utils::GetTrayIcon() {
|
||||
void* systemTray = obs_frontend_get_system_tray();
|
||||
return reinterpret_cast<QSystemTrayIcon*>(systemTray);
|
||||
}
|
||||
|
||||
void Utils::SysTrayNotify(QString text,
|
||||
QSystemTrayIcon::MessageIcon icon, QString title) {
|
||||
if (!GetConfig()->AlertsEnabled ||
|
||||
!QSystemTrayIcon::isSystemTrayAvailable() ||
|
||||
!QSystemTrayIcon::supportsMessages())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QSystemTrayIcon* trayIcon = GetTrayIcon();
|
||||
if (trayIcon)
|
||||
trayIcon->showMessage(title, text, icon);
|
||||
}
|
||||
|
||||
const char* Utils::GetRecordingFolder() {
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
QString outputMode = config_get_string(profile, "Output", "Mode");
|
||||
|
||||
if (outputMode == "Advanced") {
|
||||
// Advanced mode
|
||||
return config_get_string(profile, "AdvOut", "RecFilePath");
|
||||
} else {
|
||||
// Simple mode
|
||||
return config_get_string(profile, "SimpleOutput", "FilePath");
|
||||
}
|
||||
}
|
||||
|
||||
bool Utils::SetRecordingFolder(const char* path) {
|
||||
QDir dir(path);
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(".");
|
||||
}
|
||||
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
config_set_string(profile, "AdvOut", "RecFilePath", path);
|
||||
config_set_string(profile, "SimpleOutput", "FilePath", path);
|
||||
|
||||
config_save(profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
QString Utils::ParseDataToQueryString(obs_data_t* data) {
|
||||
if (!data)
|
||||
return QString();
|
||||
|
||||
QString query;
|
||||
|
||||
obs_data_item_t* item = obs_data_first(data);
|
||||
if (item) {
|
||||
bool isFirst = true;
|
||||
do {
|
||||
if (!obs_data_item_has_user_value(item))
|
||||
continue;
|
||||
|
||||
if (!isFirst)
|
||||
query += "&";
|
||||
else
|
||||
isFirst = false;
|
||||
|
||||
QString attrName = obs_data_item_get_name(item);
|
||||
query += (attrName + "=");
|
||||
|
||||
switch (obs_data_item_gettype(item)) {
|
||||
case OBS_DATA_BOOLEAN:
|
||||
query += (obs_data_item_get_bool(item) ? "true" : "false");
|
||||
break;
|
||||
|
||||
case OBS_DATA_NUMBER:
|
||||
switch (obs_data_item_numtype(item)) {
|
||||
case OBS_DATA_NUM_DOUBLE:
|
||||
query +=
|
||||
QString::number(obs_data_item_get_double(item));
|
||||
break;
|
||||
case OBS_DATA_NUM_INT:
|
||||
query +=
|
||||
QString::number(obs_data_item_get_int(item));
|
||||
break;
|
||||
case OBS_DATA_NUM_INVALID:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case OBS_DATA_STRING:
|
||||
query +=
|
||||
QUrl::toPercentEncoding(
|
||||
QString(obs_data_item_get_string(item)));
|
||||
break;
|
||||
|
||||
default:
|
||||
//other types are not supported
|
||||
break;
|
||||
}
|
||||
} while (obs_data_item_next(&item));
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
obs_hotkey_t* Utils::FindHotkeyByName(QString name) {
|
||||
struct current_search {
|
||||
QString query;
|
||||
obs_hotkey_t* result;
|
||||
};
|
||||
|
||||
current_search search;
|
||||
search.query = name;
|
||||
search.result = nullptr;
|
||||
|
||||
obs_enum_hotkeys([](void* data, obs_hotkey_id id, obs_hotkey_t* hotkey) {
|
||||
current_search* search = reinterpret_cast<current_search*>(data);
|
||||
|
||||
const char* hk_name = obs_hotkey_get_name(hotkey);
|
||||
if (hk_name == search->query) {
|
||||
search->result = hotkey;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, &search);
|
||||
|
||||
return search.result;
|
||||
}
|
||||
|
||||
bool Utils::ReplayBufferEnabled() {
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
QString outputMode = config_get_string(profile, "Output", "Mode");
|
||||
|
||||
if (outputMode == "Simple") {
|
||||
return config_get_bool(profile, "SimpleOutput", "RecRB");
|
||||
}
|
||||
else if (outputMode == "Advanced") {
|
||||
return config_get_bool(profile, "AdvOut", "RecRB");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Utils::StartReplayBuffer() {
|
||||
if (obs_frontend_replay_buffer_active())
|
||||
return;
|
||||
|
||||
if (!IsRPHotkeySet()) {
|
||||
obs_output_t* rpOutput = obs_frontend_get_replay_buffer_output();
|
||||
OBSData outputHotkeys = obs_hotkeys_save_output(rpOutput);
|
||||
|
||||
OBSDataAutoRelease dummyBinding = obs_data_create();
|
||||
obs_data_set_bool(dummyBinding, "control", true);
|
||||
obs_data_set_bool(dummyBinding, "alt", true);
|
||||
obs_data_set_bool(dummyBinding, "shift", true);
|
||||
obs_data_set_bool(dummyBinding, "command", true);
|
||||
obs_data_set_string(dummyBinding, "key", "OBS_KEY_0");
|
||||
|
||||
OBSDataArray rpSaveHotkey = obs_data_get_array(
|
||||
outputHotkeys, "ReplayBuffer.Save");
|
||||
obs_data_array_push_back(rpSaveHotkey, dummyBinding);
|
||||
|
||||
obs_hotkeys_load_output(rpOutput, outputHotkeys);
|
||||
obs_frontend_replay_buffer_start();
|
||||
|
||||
obs_output_release(rpOutput);
|
||||
}
|
||||
else {
|
||||
obs_frontend_replay_buffer_start();
|
||||
}
|
||||
}
|
||||
|
||||
bool Utils::IsRPHotkeySet() {
|
||||
OBSOutputAutoRelease rpOutput = obs_frontend_get_replay_buffer_output();
|
||||
OBSDataAutoRelease hotkeys = obs_hotkeys_save_output(rpOutput);
|
||||
OBSDataArrayAutoRelease bindings = obs_data_get_array(hotkeys,
|
||||
"ReplayBuffer.Save");
|
||||
|
||||
size_t count = obs_data_array_count(bindings);
|
||||
return (count > 0);
|
||||
}
|
||||
|
||||
const char* Utils::GetFilenameFormatting() {
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
return config_get_string(profile, "Output", "FilenameFormatting");
|
||||
}
|
||||
|
||||
bool Utils::SetFilenameFormatting(const char* filenameFormatting) {
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
config_set_string(profile, "Output", "FilenameFormatting", filenameFormatting);
|
||||
config_save(profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* Utils::GetCurrentRecordingFilename()
|
||||
{
|
||||
OBSOutputAutoRelease recordingOutput = obs_frontend_get_recording_output();
|
||||
if (!recordingOutput) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease settings = obs_output_get_settings(recordingOutput);
|
||||
|
||||
// mimicks the behavior of BasicOutputHandler::GetRecordingFilename :
|
||||
// try to fetch the path from the "url" property, then try "path" if the first one
|
||||
// didn't yield any result
|
||||
OBSDataItemAutoRelease item = obs_data_item_byname(settings, "url");
|
||||
if (!item) {
|
||||
item = obs_data_item_byname(settings, "path");
|
||||
if (!item) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return obs_data_item_get_string(item);
|
||||
}
|
||||
|
||||
// Transform properties copy-pasted from WSRequestHandler_SceneItems.cpp because typedefs can't be extended yet
|
||||
|
||||
/**
|
||||
* @typedef {Object} `SceneItemTransform`
|
||||
* @property {double} `position.x` The x position of the scene item from the left.
|
||||
* @property {double} `position.y` The y position of the scene item from the top.
|
||||
* @property {int} `position.alignment` The point on the scene item that the item is manipulated from.
|
||||
* @property {double} `rotation` The clockwise rotation of the scene item in degrees around the point of alignment.
|
||||
* @property {double} `scale.x` The x-scale factor of the scene item.
|
||||
* @property {double} `scale.y` The y-scale factor of the scene item.
|
||||
* @property {int} `crop.top` The number of pixels cropped off the top of the scene item before scaling.
|
||||
* @property {int} `crop.right` The number of pixels cropped off the right of the scene item before scaling.
|
||||
* @property {int} `crop.bottom` The number of pixels cropped off the bottom of the scene item before scaling.
|
||||
* @property {int} `crop.left` The number of pixels cropped off the left of the scene item before scaling.
|
||||
* @property {bool} `visible` If the scene item is visible.
|
||||
* @property {bool} `locked` If the scene item is locked in position.
|
||||
* @property {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
|
||||
* @property {int} `bounds.alignment` Alignment of the bounding box.
|
||||
* @property {double} `bounds.x` Width of the bounding box.
|
||||
* @property {double} `bounds.y` Height of the bounding box.
|
||||
* @property {int} `sourceWidth` Base width (without scaling) of the source
|
||||
* @property {int} `sourceHeight` Base source (without scaling) of the source
|
||||
* @property {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)
|
||||
* @property {double} `height` Scene item height (base source height multiplied by the vertical scaling factor)
|
||||
* @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
|
||||
* @property {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)
|
||||
*/
|
||||
obs_data_t* Utils::GetSceneItemPropertiesData(obs_sceneitem_t* sceneItem) {
|
||||
if (!sceneItem) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OBSSource source = obs_sceneitem_get_source(sceneItem);
|
||||
uint32_t baseSourceWidth = obs_source_get_width(source);
|
||||
uint32_t baseSourceHeight = obs_source_get_height(source);
|
||||
|
||||
vec2 pos, scale, bounds;
|
||||
obs_sceneitem_crop crop;
|
||||
|
||||
obs_sceneitem_get_pos(sceneItem, &pos);
|
||||
obs_sceneitem_get_scale(sceneItem, &scale);
|
||||
obs_sceneitem_get_crop(sceneItem, &crop);
|
||||
obs_sceneitem_get_bounds(sceneItem, &bounds);
|
||||
|
||||
uint32_t alignment = obs_sceneitem_get_alignment(sceneItem);
|
||||
float rotation = obs_sceneitem_get_rot(sceneItem);
|
||||
bool isVisible = obs_sceneitem_visible(sceneItem);
|
||||
bool isLocked = obs_sceneitem_locked(sceneItem);
|
||||
|
||||
obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(sceneItem);
|
||||
uint32_t boundsAlignment = obs_sceneitem_get_bounds_alignment(sceneItem);
|
||||
QString boundsTypeName = getBoundsNameFromType(boundsType);
|
||||
|
||||
OBSDataAutoRelease posData = obs_data_create();
|
||||
obs_data_set_double(posData, "x", pos.x);
|
||||
obs_data_set_double(posData, "y", pos.y);
|
||||
obs_data_set_int(posData, "alignment", alignment);
|
||||
|
||||
OBSDataAutoRelease scaleData = obs_data_create();
|
||||
obs_data_set_double(scaleData, "x", scale.x);
|
||||
obs_data_set_double(scaleData, "y", scale.y);
|
||||
|
||||
OBSDataAutoRelease cropData = obs_data_create();
|
||||
obs_data_set_int(cropData, "left", crop.left);
|
||||
obs_data_set_int(cropData, "top", crop.top);
|
||||
obs_data_set_int(cropData, "right", crop.right);
|
||||
obs_data_set_int(cropData, "bottom", crop.bottom);
|
||||
|
||||
OBSDataAutoRelease boundsData = obs_data_create();
|
||||
obs_data_set_string(boundsData, "type", boundsTypeName.toUtf8());
|
||||
obs_data_set_int(boundsData, "alignment", boundsAlignment);
|
||||
obs_data_set_double(boundsData, "x", bounds.x);
|
||||
obs_data_set_double(boundsData, "y", bounds.y);
|
||||
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_obj(data, "position", posData);
|
||||
obs_data_set_double(data, "rotation", rotation);
|
||||
obs_data_set_obj(data, "scale", scaleData);
|
||||
obs_data_set_obj(data, "crop", cropData);
|
||||
obs_data_set_bool(data, "visible", isVisible);
|
||||
obs_data_set_bool(data, "locked", isLocked);
|
||||
obs_data_set_obj(data, "bounds", boundsData);
|
||||
|
||||
obs_data_set_int(data, "sourceWidth", baseSourceWidth);
|
||||
obs_data_set_int(data, "sourceHeight", baseSourceHeight);
|
||||
obs_data_set_double(data, "width", baseSourceWidth * scale.x);
|
||||
obs_data_set_double(data, "height", baseSourceHeight * scale.y);
|
||||
|
||||
obs_scene_t* parent = obs_sceneitem_get_scene(sceneItem);
|
||||
if (parent) {
|
||||
OBSSource parentSource = obs_scene_get_source(parent);
|
||||
QString parentKind = obs_source_get_id(parentSource);
|
||||
if (parentKind == "group") {
|
||||
obs_data_set_string(data, "parentGroupName", obs_source_get_name(parentSource));
|
||||
}
|
||||
}
|
||||
|
||||
if (obs_sceneitem_is_group(sceneItem)) {
|
||||
OBSDataArrayAutoRelease children = obs_data_array_create();
|
||||
obs_sceneitem_group_enum_items(sceneItem, [](obs_scene_t*, obs_sceneitem_t* subItem, void* param) {
|
||||
obs_data_array_t* items = reinterpret_cast<obs_data_array_t*>(param);
|
||||
|
||||
OBSDataAutoRelease itemData = GetSceneItemPropertiesData(subItem);
|
||||
obs_data_array_push_back(items, itemData);
|
||||
|
||||
return true;
|
||||
}, children);
|
||||
obs_data_set_array(data, "groupChildren", children);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
obs_data_t* Utils::GetSourceFilterInfo(obs_source_t* filter, bool includeSettings)
|
||||
{
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_bool(data, "enabled", obs_source_enabled(filter));
|
||||
obs_data_set_string(data, "type", obs_source_get_id(filter));
|
||||
obs_data_set_string(data, "name", obs_source_get_name(filter));
|
||||
if (includeSettings) {
|
||||
OBSDataAutoRelease settings = obs_source_get_settings(filter);
|
||||
obs_data_set_obj(data, "settings", settings);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetSourceFiltersList(obs_source_t* source, bool includeSettings)
|
||||
{
|
||||
struct enum_params {
|
||||
obs_data_array_t* filters;
|
||||
bool includeSettings;
|
||||
};
|
||||
|
||||
if (!source) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct enum_params enumParams;
|
||||
|
||||
enumParams.filters = obs_data_array_create();
|
||||
enumParams.includeSettings = includeSettings;
|
||||
|
||||
obs_source_enum_filters(source, [](obs_source_t* parent, obs_source_t* child, void* param)
|
||||
{
|
||||
auto enumParams = reinterpret_cast<struct enum_params*>(param);
|
||||
|
||||
OBSDataAutoRelease filterData = Utils::GetSourceFilterInfo(child, enumParams->includeSettings);
|
||||
obs_data_array_push_back(enumParams->filters, filterData);
|
||||
}, &enumParams);
|
||||
|
||||
return enumParams.filters;
|
||||
}
|
||||
|
||||
void getPauseRecordingFunctions(RecordingPausedFunction* recPausedFuncPtr, PauseRecordingFunction* pauseRecFuncPtr)
|
||||
{
|
||||
void* frontendApi = os_dlopen("obs-frontend-api");
|
||||
|
||||
if (recPausedFuncPtr) {
|
||||
*recPausedFuncPtr = (RecordingPausedFunction)os_dlsym(frontendApi, "obs_frontend_recording_paused");
|
||||
}
|
||||
|
||||
if (pauseRecFuncPtr) {
|
||||
*pauseRecFuncPtr = (PauseRecordingFunction)os_dlsym(frontendApi, "obs_frontend_recording_pause");
|
||||
}
|
||||
}
|
||||
|
||||
QString Utils::nsToTimestamp(uint64_t ns)
|
||||
{
|
||||
uint64_t ms = ns / 1000000ULL;
|
||||
uint64_t secs = ms / 1000ULL;
|
||||
uint64_t minutes = secs / 60ULL;
|
||||
|
||||
uint64_t hoursPart = minutes / 60ULL;
|
||||
uint64_t minutesPart = minutes % 60ULL;
|
||||
uint64_t secsPart = secs % 60ULL;
|
||||
uint64_t msPart = ms % 1000ULL;
|
||||
|
||||
return QString::asprintf("%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 ".%03" PRIu64, hoursPart, minutesPart, secsPart, msPart);
|
||||
}
|
||||
|
||||
void Utils::AddSourceHelper(void *_data, obs_scene_t *scene)
|
||||
{
|
||||
auto *data = reinterpret_cast<AddSourceData*>(_data);
|
||||
data->sceneItem = obs_scene_add(scene, data->source);
|
||||
obs_sceneitem_set_visible(data->sceneItem, data->setVisible);
|
||||
}
|
||||
|
||||
obs_data_t *Utils::OBSDataGetDefaults(obs_data_t *data)
|
||||
{
|
||||
obs_data_t *returnData = obs_data_create();
|
||||
obs_data_item_t *item = NULL;
|
||||
|
||||
for (item = obs_data_first(data); item; obs_data_item_next(&item)) {
|
||||
enum obs_data_type type = obs_data_item_gettype(item);
|
||||
const char *name = obs_data_item_get_name(item);
|
||||
|
||||
if (type == OBS_DATA_STRING) {
|
||||
const char *val = obs_data_item_get_string(item);
|
||||
obs_data_set_string(returnData, name, val);
|
||||
} else if (type == OBS_DATA_NUMBER) {
|
||||
enum obs_data_number_type type = obs_data_item_numtype(item);
|
||||
if (type == OBS_DATA_NUM_INT) {
|
||||
long long val = obs_data_item_get_int(item);
|
||||
obs_data_set_int(returnData, name, val);
|
||||
} else {
|
||||
double val = obs_data_item_get_double(item);
|
||||
obs_data_set_double(returnData, name, val);
|
||||
}
|
||||
} else if (type == OBS_DATA_BOOLEAN) {
|
||||
bool val = obs_data_item_get_bool(item);
|
||||
obs_data_set_bool(returnData, name, val);
|
||||
} else if (type == OBS_DATA_OBJECT) {
|
||||
OBSDataAutoRelease obj = obs_data_item_get_obj(item);
|
||||
obs_data_set_obj(returnData, name, obj);
|
||||
} else if (type == OBS_DATA_ARRAY) {
|
||||
OBSDataArrayAutoRelease array = obs_data_item_get_array(item);
|
||||
obs_data_set_array(returnData, name, array);
|
||||
}
|
||||
}
|
||||
return returnData;
|
||||
}
|
99
src/Utils.h
Normal file
99
src/Utils.h
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtWidgets/QSpinBox>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QLayout>
|
||||
#include <QtWidgets/QListWidget>
|
||||
#include <QtWidgets/QSystemTrayIcon>
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <obs-module.h>
|
||||
#include <util/config-file.h>
|
||||
|
||||
typedef void(*PauseRecordingFunction)(bool);
|
||||
typedef bool(*RecordingPausedFunction)();
|
||||
|
||||
namespace Utils {
|
||||
bool StringInStringList(char** strings, const char* string);
|
||||
obs_data_array_t* StringListToArray(char** strings, const char* key);
|
||||
obs_data_array_t* GetSceneItems(obs_source_t* source);
|
||||
obs_data_t* GetSceneItemData(obs_sceneitem_t* item);
|
||||
|
||||
// These functions support nested lookup into groups
|
||||
obs_sceneitem_t* GetSceneItemFromName(obs_scene_t* scene, QString name);
|
||||
obs_sceneitem_t* GetSceneItemFromId(obs_scene_t* scene, int64_t id);
|
||||
obs_sceneitem_t* GetSceneItemFromItem(obs_scene_t* scene, obs_data_t* item);
|
||||
obs_sceneitem_t* GetSceneItemFromRequestField(obs_scene_t* scene, obs_data_item_t* dataItem);
|
||||
|
||||
obs_scene_t* GetSceneFromNameOrCurrent(QString sceneName);
|
||||
obs_data_t* GetSceneItemPropertiesData(obs_sceneitem_t* item);
|
||||
|
||||
obs_data_t* GetSourceFilterInfo(obs_source_t* filter, bool includeSettings);
|
||||
obs_data_array_t* GetSourceFiltersList(obs_source_t* source, bool includeSettings);
|
||||
|
||||
bool IsValidAlignment(const uint32_t alignment);
|
||||
|
||||
obs_data_array_t* GetScenes();
|
||||
obs_data_t* GetSceneData(obs_source_t* source);
|
||||
|
||||
// TODO contribute a proper frontend API method for this to OBS and remove this hack
|
||||
QSpinBox* GetTransitionDurationControl();
|
||||
int GetTransitionDuration(obs_source_t* transition);
|
||||
obs_source_t* GetTransitionFromName(QString transitionName);
|
||||
bool SetTransitionByName(QString transitionName);
|
||||
obs_data_t* GetTransitionData(obs_source_t* transition);
|
||||
|
||||
QString OBSVersionString();
|
||||
|
||||
QSystemTrayIcon* GetTrayIcon();
|
||||
void SysTrayNotify(
|
||||
QString text,
|
||||
QSystemTrayIcon::MessageIcon n,
|
||||
QString title = QString("obs-websocket"));
|
||||
|
||||
const char* GetRecordingFolder();
|
||||
bool SetRecordingFolder(const char* path);
|
||||
|
||||
QString ParseDataToQueryString(obs_data_t* data);
|
||||
obs_hotkey_t* FindHotkeyByName(QString name);
|
||||
|
||||
bool ReplayBufferEnabled();
|
||||
void StartReplayBuffer();
|
||||
bool IsRPHotkeySet();
|
||||
|
||||
const char* GetFilenameFormatting();
|
||||
bool SetFilenameFormatting(const char* filenameFormatting);
|
||||
|
||||
const char* GetCurrentRecordingFilename();
|
||||
|
||||
QString nsToTimestamp(uint64_t ns);
|
||||
struct AddSourceData {
|
||||
obs_source_t *source;
|
||||
obs_sceneitem_t *sceneItem;
|
||||
bool setVisible;
|
||||
};
|
||||
void AddSourceHelper(void *_data, obs_scene_t *scene);
|
||||
|
||||
obs_data_t *OBSDataGetDefaults(obs_data_t *data);
|
||||
};
|
2056
src/WSEvents.cpp
Normal file
2056
src/WSEvents.cpp
Normal file
File diff suppressed because it is too large
Load Diff
156
src/WSEvents.h
Normal file
156
src/WSEvents.h
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <util/platform.h>
|
||||
|
||||
#include <QtWidgets/QListWidgetItem>
|
||||
#include <QtCore/QSharedPointer>
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
#include "WSServer.h"
|
||||
|
||||
class WSEvents : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WSEvents(WSServerPtr srv);
|
||||
~WSEvents();
|
||||
|
||||
void connectSourceSignals(obs_source_t* source);
|
||||
void disconnectSourceSignals(obs_source_t* source);
|
||||
|
||||
void connectFilterSignals(obs_source_t* filter);
|
||||
void disconnectFilterSignals(obs_source_t* filter);
|
||||
|
||||
void hookTransitionPlaybackEvents();
|
||||
void unhookTransitionPlaybackEvents();
|
||||
|
||||
uint64_t getStreamingTime();
|
||||
uint64_t getRecordingTime();
|
||||
|
||||
QString getStreamingTimecode();
|
||||
QString getRecordingTimecode();
|
||||
|
||||
obs_data_t* GetStats();
|
||||
|
||||
void OnBroadcastCustomMessage(QString realm, obs_data_t* data);
|
||||
|
||||
bool HeartbeatIsActive;
|
||||
|
||||
private slots:
|
||||
void StreamStatus();
|
||||
void Heartbeat();
|
||||
void TransitionDurationChanged(int ms);
|
||||
|
||||
private:
|
||||
WSServerPtr _srv;
|
||||
QTimer streamStatusTimer;
|
||||
QTimer heartbeatTimer;
|
||||
os_cpu_usage_info_t* cpuUsageInfo;
|
||||
|
||||
bool pulse;
|
||||
|
||||
uint64_t _streamStarttime;
|
||||
|
||||
uint64_t _lastBytesSent;
|
||||
uint64_t _lastBytesSentTime;
|
||||
|
||||
void broadcastUpdate(const char* updateType,
|
||||
obs_data_t* additionalFields);
|
||||
|
||||
void OnSceneChange();
|
||||
void OnSceneListChange();
|
||||
void OnSceneCollectionChange();
|
||||
void OnSceneCollectionListChange();
|
||||
|
||||
void OnTransitionChange();
|
||||
void OnTransitionListChange();
|
||||
|
||||
void OnProfileChange();
|
||||
void OnProfileListChange();
|
||||
|
||||
void OnStreamStarting();
|
||||
void OnStreamStarted();
|
||||
void OnStreamStopping();
|
||||
void OnStreamStopped();
|
||||
|
||||
void OnRecordingStarting();
|
||||
void OnRecordingStarted();
|
||||
void OnRecordingStopping();
|
||||
void OnRecordingStopped();
|
||||
void OnRecordingPaused();
|
||||
void OnRecordingResumed();
|
||||
|
||||
void OnReplayStarting();
|
||||
void OnReplayStarted();
|
||||
void OnReplayStopping();
|
||||
void OnReplayStopped();
|
||||
|
||||
void OnStudioModeSwitched(bool enabled);
|
||||
void OnPreviewSceneChanged();
|
||||
|
||||
void OnExit();
|
||||
|
||||
static void FrontendEventHandler(
|
||||
enum obs_frontend_event event, void* privateData);
|
||||
|
||||
static void OnTransitionBegin(void* param, calldata_t* data);
|
||||
static void OnTransitionEnd(void* param, calldata_t* data);
|
||||
static void OnTransitionVideoEnd(void* param, calldata_t* data);
|
||||
|
||||
static void OnSourceCreate(void* param, calldata_t* data);
|
||||
static void OnSourceDestroy(void* param, calldata_t* data);
|
||||
|
||||
static void OnSourceVolumeChange(void* param, calldata_t* data);
|
||||
static void OnSourceMuteStateChange(void* param, calldata_t* data);
|
||||
static void OnSourceAudioSyncOffsetChanged(void* param, calldata_t* data);
|
||||
static void OnSourceAudioMixersChanged(void* param, calldata_t* data);
|
||||
static void OnSourceAudioActivated(void* param, calldata_t* data);
|
||||
static void OnSourceAudioDeactivated(void* param, calldata_t* data);
|
||||
|
||||
static void OnSourceRename(void* param, calldata_t* data);
|
||||
|
||||
static void OnSourceFilterAdded(void* param, calldata_t* data);
|
||||
static void OnSourceFilterRemoved(void* param, calldata_t* data);
|
||||
static void OnSourceFilterVisibilityChanged(void* param, calldata_t* data);
|
||||
static void OnSourceFilterOrderChanged(void* param, calldata_t* data);
|
||||
|
||||
static void OnMediaPlaying(void* param, calldata_t* data);
|
||||
static void OnMediaPaused(void* param, calldata_t* data);
|
||||
static void OnMediaRestarted(void* param, calldata_t* data);
|
||||
static void OnMediaStopped(void* param, calldata_t* data);
|
||||
static void OnMediaNext(void* param, calldata_t* data);
|
||||
static void OnMediaPrevious(void* param, calldata_t* data);
|
||||
static void OnMediaStarted(void* param, calldata_t* data);
|
||||
static void OnMediaEnded(void* param, calldata_t* data);
|
||||
|
||||
static void OnSceneReordered(void* param, calldata_t* data);
|
||||
static void OnSceneItemAdd(void* param, calldata_t* data);
|
||||
static void OnSceneItemDelete(void* param, calldata_t* data);
|
||||
static void OnSceneItemVisibilityChanged(void* param, calldata_t* data);
|
||||
static void OnSceneItemLockChanged(void* param, calldata_t* data);
|
||||
static void OnSceneItemTransform(void* param, calldata_t* data);
|
||||
static void OnSceneItemSelected(void* param, calldata_t* data);
|
||||
static void OnSceneItemDeselected(void* param, calldata_t* data);
|
||||
};
|
210
src/WSRequestHandler.cpp
Normal file
210
src/WSRequestHandler.cpp
Normal file
@ -0,0 +1,210 @@
|
||||
/**
|
||||
* obs-websocket
|
||||
* Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
* Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <obs-data.h>
|
||||
|
||||
#include "Config.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
using namespace std::placeholders;
|
||||
|
||||
const QHash<QString, RpcMethodHandler> WSRequestHandler::messageMap{
|
||||
// Category: General
|
||||
{ "GetVersion", &WSRequestHandler::GetVersion },
|
||||
{ "GetAuthRequired", &WSRequestHandler::GetAuthRequired },
|
||||
{ "Authenticate", &WSRequestHandler::Authenticate },
|
||||
{ "SetHeartbeat", &WSRequestHandler::SetHeartbeat },
|
||||
{ "SetFilenameFormatting", &WSRequestHandler::SetFilenameFormatting },
|
||||
{ "GetFilenameFormatting", &WSRequestHandler::GetFilenameFormatting },
|
||||
{ "GetStats", &WSRequestHandler::GetStats },
|
||||
{ "BroadcastCustomMessage", &WSRequestHandler::BroadcastCustomMessage },
|
||||
{ "GetVideoInfo", &WSRequestHandler::GetVideoInfo },
|
||||
{ "OpenProjector", &WSRequestHandler::OpenProjector },
|
||||
{ "TriggerHotkeyByName", &WSRequestHandler::TriggerHotkeyByName },
|
||||
{ "TriggerHotkeyBySequence", &WSRequestHandler::TriggerHotkeyBySequence },
|
||||
{ "ExecuteBatch", &WSRequestHandler::ExecuteBatch },
|
||||
|
||||
// Category: Media Control
|
||||
{ "PlayPauseMedia", &WSRequestHandler::PlayPauseMedia },
|
||||
{ "RestartMedia", &WSRequestHandler::RestartMedia },
|
||||
{ "StopMedia", &WSRequestHandler::StopMedia },
|
||||
{ "NextMedia", &WSRequestHandler::NextMedia },
|
||||
{ "PreviousMedia", &WSRequestHandler::PreviousMedia },
|
||||
{ "GetMediaDuration", &WSRequestHandler::GetMediaDuration },
|
||||
{ "GetMediaTime", &WSRequestHandler::GetMediaTime },
|
||||
{ "SetMediaTime", &WSRequestHandler::SetMediaTime },
|
||||
{ "ScrubMedia", &WSRequestHandler::ScrubMedia },
|
||||
{ "GetMediaState", &WSRequestHandler::GetMediaState },
|
||||
{ "GetMediaSourcesList", &WSRequestHandler::GetMediaSourcesList },
|
||||
|
||||
// Category: Outputs
|
||||
{ "ListOutputs", &WSRequestHandler::ListOutputs },
|
||||
{ "GetOutputInfo", &WSRequestHandler::GetOutputInfo },
|
||||
{ "StartOutput", &WSRequestHandler::StartOutput },
|
||||
{ "StopOutput", &WSRequestHandler::StopOutput },
|
||||
|
||||
// Category: Profiles
|
||||
{ "SetCurrentProfile", &WSRequestHandler::SetCurrentProfile },
|
||||
{ "GetCurrentProfile", &WSRequestHandler::GetCurrentProfile },
|
||||
{ "ListProfiles", &WSRequestHandler::ListProfiles },
|
||||
|
||||
// Category: Recording
|
||||
{ "GetRecordingStatus", &WSRequestHandler::GetRecordingStatus },
|
||||
{ "StartStopRecording", &WSRequestHandler::StartStopRecording },
|
||||
{ "StartRecording", &WSRequestHandler::StartRecording },
|
||||
{ "StopRecording", &WSRequestHandler::StopRecording },
|
||||
{ "PauseRecording", &WSRequestHandler::PauseRecording },
|
||||
{ "ResumeRecording", &WSRequestHandler::ResumeRecording },
|
||||
{ "SetRecordingFolder", &WSRequestHandler::SetRecordingFolder },
|
||||
{ "GetRecordingFolder", &WSRequestHandler::GetRecordingFolder },
|
||||
|
||||
// Category: Replay Buffer
|
||||
{ "GetReplayBufferStatus", &WSRequestHandler::GetReplayBufferStatus },
|
||||
{ "StartStopReplayBuffer", &WSRequestHandler::StartStopReplayBuffer },
|
||||
{ "StartReplayBuffer", &WSRequestHandler::StartReplayBuffer },
|
||||
{ "StopReplayBuffer", &WSRequestHandler::StopReplayBuffer },
|
||||
{ "SaveReplayBuffer", &WSRequestHandler::SaveReplayBuffer },
|
||||
|
||||
// Category: Scene Collections
|
||||
{ "SetCurrentSceneCollection", &WSRequestHandler::SetCurrentSceneCollection },
|
||||
{ "GetCurrentSceneCollection", &WSRequestHandler::GetCurrentSceneCollection },
|
||||
{ "ListSceneCollections", &WSRequestHandler::ListSceneCollections },
|
||||
|
||||
// Category: Scene Items
|
||||
{ "GetSceneItemList", &WSRequestHandler::GetSceneItemList },
|
||||
{ "GetSceneItemProperties", &WSRequestHandler::GetSceneItemProperties },
|
||||
{ "SetSceneItemProperties", &WSRequestHandler::SetSceneItemProperties },
|
||||
{ "ResetSceneItem", &WSRequestHandler::ResetSceneItem },
|
||||
{ "SetSceneItemRender", &WSRequestHandler::SetSceneItemRender },
|
||||
{ "SetSceneItemPosition", &WSRequestHandler::SetSceneItemPosition },
|
||||
{ "SetSceneItemTransform", &WSRequestHandler::SetSceneItemTransform },
|
||||
{ "SetSceneItemCrop", &WSRequestHandler::SetSceneItemCrop },
|
||||
{ "SetSourceRender", &WSRequestHandler::SetSceneItemRender }, // Retrocompat TODO: Remove in 5.0.0
|
||||
{ "DeleteSceneItem", &WSRequestHandler::DeleteSceneItem },
|
||||
{ "AddSceneItem", &WSRequestHandler::AddSceneItem },
|
||||
{ "DuplicateSceneItem", &WSRequestHandler::DuplicateSceneItem },
|
||||
|
||||
// Category: Scenes
|
||||
{ "SetCurrentScene", &WSRequestHandler::SetCurrentScene },
|
||||
{ "GetCurrentScene", &WSRequestHandler::GetCurrentScene },
|
||||
{ "GetSceneList", &WSRequestHandler::GetSceneList },
|
||||
{ "CreateScene", &WSRequestHandler::CreateScene },
|
||||
{ "ReorderSceneItems", &WSRequestHandler::ReorderSceneItems },
|
||||
{ "SetSceneTransitionOverride", &WSRequestHandler::SetSceneTransitionOverride },
|
||||
{ "RemoveSceneTransitionOverride", &WSRequestHandler::RemoveSceneTransitionOverride },
|
||||
{ "GetSceneTransitionOverride", &WSRequestHandler::GetSceneTransitionOverride },
|
||||
|
||||
// Category: Sources
|
||||
{ "CreateSource", &WSRequestHandler::CreateSource },
|
||||
{ "GetSourcesList", &WSRequestHandler::GetSourcesList },
|
||||
{ "GetSourceTypesList", &WSRequestHandler::GetSourceTypesList },
|
||||
{ "GetVolume", &WSRequestHandler::GetVolume },
|
||||
{ "SetVolume", &WSRequestHandler::SetVolume },
|
||||
{ "GetMute", &WSRequestHandler::GetMute },
|
||||
{ "SetMute", &WSRequestHandler::SetMute },
|
||||
{ "ToggleMute", &WSRequestHandler::ToggleMute },
|
||||
{ "GetAudioActive", &WSRequestHandler::GetAudioActive },
|
||||
{ "SetSourceName", &WSRequestHandler::SetSourceName },
|
||||
{ "SetSyncOffset", &WSRequestHandler::SetSyncOffset },
|
||||
{ "GetSyncOffset", &WSRequestHandler::GetSyncOffset },
|
||||
{ "GetSourceSettings", &WSRequestHandler::GetSourceSettings },
|
||||
{ "SetSourceSettings", &WSRequestHandler::SetSourceSettings },
|
||||
{ "GetTextGDIPlusProperties", &WSRequestHandler::GetTextGDIPlusProperties },
|
||||
{ "SetTextGDIPlusProperties", &WSRequestHandler::SetTextGDIPlusProperties },
|
||||
{ "GetTextFreetype2Properties", &WSRequestHandler::GetTextFreetype2Properties },
|
||||
{ "SetTextFreetype2Properties", &WSRequestHandler::SetTextFreetype2Properties },
|
||||
{ "GetBrowserSourceProperties", &WSRequestHandler::GetBrowserSourceProperties },
|
||||
{ "SetBrowserSourceProperties", &WSRequestHandler::SetBrowserSourceProperties },
|
||||
{ "GetSpecialSources", &WSRequestHandler::GetSpecialSources },
|
||||
{ "GetSourceFilters", &WSRequestHandler::GetSourceFilters },
|
||||
{ "GetSourceFilterInfo", &WSRequestHandler::GetSourceFilterInfo },
|
||||
{ "AddFilterToSource", &WSRequestHandler::AddFilterToSource },
|
||||
{ "RemoveFilterFromSource", &WSRequestHandler::RemoveFilterFromSource },
|
||||
{ "ReorderSourceFilter", &WSRequestHandler::ReorderSourceFilter },
|
||||
{ "MoveSourceFilter", &WSRequestHandler::MoveSourceFilter },
|
||||
{ "SetSourceFilterSettings", &WSRequestHandler::SetSourceFilterSettings },
|
||||
{ "SetSourceFilterVisibility", &WSRequestHandler::SetSourceFilterVisibility },
|
||||
{ "GetAudioMonitorType", &WSRequestHandler::GetAudioMonitorType },
|
||||
{ "SetAudioMonitorType", &WSRequestHandler::SetAudioMonitorType },
|
||||
{ "GetSourceDefaultSettings", &WSRequestHandler::GetSourceDefaultSettings },
|
||||
{ "TakeSourceScreenshot", &WSRequestHandler::TakeSourceScreenshot },
|
||||
{ "RefreshBrowserSource", &WSRequestHandler::RefreshBrowserSource },
|
||||
|
||||
// Category: Streaming
|
||||
{ "GetStreamingStatus", &WSRequestHandler::GetStreamingStatus },
|
||||
{ "StartStopStreaming", &WSRequestHandler::StartStopStreaming },
|
||||
{ "StartStreaming", &WSRequestHandler::StartStreaming },
|
||||
{ "StopStreaming", &WSRequestHandler::StopStreaming },
|
||||
{ "SetStreamSettings", &WSRequestHandler::SetStreamSettings },
|
||||
{ "GetStreamSettings", &WSRequestHandler::GetStreamSettings },
|
||||
{ "SaveStreamSettings", &WSRequestHandler::SaveStreamSettings },
|
||||
{ "SendCaptions", &WSRequestHandler::SendCaptions },
|
||||
|
||||
// Category: Studio Mode
|
||||
{ "GetStudioModeStatus", &WSRequestHandler::GetStudioModeStatus },
|
||||
{ "GetPreviewScene", &WSRequestHandler::GetPreviewScene },
|
||||
{ "SetPreviewScene", &WSRequestHandler::SetPreviewScene },
|
||||
{ "TransitionToProgram", &WSRequestHandler::TransitionToProgram },
|
||||
{ "EnableStudioMode", &WSRequestHandler::EnableStudioMode },
|
||||
{ "DisableStudioMode", &WSRequestHandler::DisableStudioMode },
|
||||
{ "ToggleStudioMode", &WSRequestHandler::ToggleStudioMode },
|
||||
|
||||
// Category: Transitions
|
||||
{ "GetTransitionList", &WSRequestHandler::GetTransitionList },
|
||||
{ "GetCurrentTransition", &WSRequestHandler::GetCurrentTransition },
|
||||
{ "SetCurrentTransition", &WSRequestHandler::SetCurrentTransition },
|
||||
{ "SetTransitionDuration", &WSRequestHandler::SetTransitionDuration },
|
||||
{ "GetTransitionDuration", &WSRequestHandler::GetTransitionDuration },
|
||||
{ "GetTransitionPosition", &WSRequestHandler::GetTransitionPosition },
|
||||
{ "GetTransitionSettings", &WSRequestHandler::GetTransitionSettings },
|
||||
{ "SetTransitionSettings", &WSRequestHandler::SetTransitionSettings },
|
||||
{ "ReleaseTBar", &WSRequestHandler::ReleaseTBar },
|
||||
{ "SetTBarPosition", &WSRequestHandler::SetTBarPosition }
|
||||
};
|
||||
|
||||
const QSet<QString> WSRequestHandler::authNotRequired {
|
||||
"GetVersion",
|
||||
"GetAuthRequired",
|
||||
"Authenticate"
|
||||
};
|
||||
|
||||
WSRequestHandler::WSRequestHandler(ConnectionProperties& connProperties) :
|
||||
_connProperties(connProperties)
|
||||
{
|
||||
}
|
||||
|
||||
RpcResponse WSRequestHandler::processRequest(const RpcRequest& request) {
|
||||
if (GetConfig()->AuthRequired
|
||||
&& (!authNotRequired.contains(request.methodName()))
|
||||
&& (!_connProperties.isAuthenticated()))
|
||||
{
|
||||
return RpcResponse::fail(request, "Not Authenticated");
|
||||
}
|
||||
|
||||
RpcMethodHandler handlerFunc = messageMap[request.methodName()];
|
||||
if (!handlerFunc) {
|
||||
return RpcResponse::fail(request, "invalid request type");
|
||||
}
|
||||
|
||||
return std::bind(handlerFunc, this, _1)(request);
|
||||
}
|
200
src/WSRequestHandler.h
Normal file
200
src/WSRequestHandler.h
Normal file
@ -0,0 +1,200 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QSet>
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include "ConnectionProperties.h"
|
||||
|
||||
#include "rpc/RpcRequest.h"
|
||||
#include "rpc/RpcResponse.h"
|
||||
|
||||
#include "obs-websocket.h"
|
||||
|
||||
class WSRequestHandler;
|
||||
typedef RpcResponse(WSRequestHandler::*RpcMethodHandler)(const RpcRequest&);
|
||||
|
||||
class WSRequestHandler {
|
||||
public:
|
||||
explicit WSRequestHandler(ConnectionProperties& connProperties);
|
||||
RpcResponse processRequest(const RpcRequest& textMessage);
|
||||
|
||||
private:
|
||||
ConnectionProperties& _connProperties;
|
||||
|
||||
static const QHash<QString, RpcMethodHandler> messageMap;
|
||||
static const QSet<QString> authNotRequired;
|
||||
|
||||
// Category: General
|
||||
RpcResponse GetVersion(const RpcRequest&);
|
||||
RpcResponse GetAuthRequired(const RpcRequest&);
|
||||
RpcResponse Authenticate(const RpcRequest&);
|
||||
RpcResponse SetHeartbeat(const RpcRequest&);
|
||||
RpcResponse SetFilenameFormatting(const RpcRequest&);
|
||||
RpcResponse GetFilenameFormatting(const RpcRequest&);
|
||||
RpcResponse GetStats(const RpcRequest&);
|
||||
RpcResponse BroadcastCustomMessage(const RpcRequest&);
|
||||
RpcResponse GetVideoInfo(const RpcRequest&);
|
||||
RpcResponse OpenProjector(const RpcRequest&);
|
||||
RpcResponse TriggerHotkeyByName(const RpcRequest&);
|
||||
RpcResponse TriggerHotkeyBySequence(const RpcRequest&);
|
||||
RpcResponse ExecuteBatch(const RpcRequest&);
|
||||
|
||||
// Category: Media Control
|
||||
RpcResponse PlayPauseMedia(const RpcRequest&);
|
||||
RpcResponse RestartMedia(const RpcRequest&);
|
||||
RpcResponse StopMedia(const RpcRequest&);
|
||||
RpcResponse NextMedia(const RpcRequest&);
|
||||
RpcResponse PreviousMedia(const RpcRequest&);
|
||||
RpcResponse GetMediaDuration(const RpcRequest&);
|
||||
RpcResponse GetMediaTime(const RpcRequest&);
|
||||
RpcResponse SetMediaTime(const RpcRequest&);
|
||||
RpcResponse ScrubMedia(const RpcRequest&);
|
||||
RpcResponse GetMediaState(const RpcRequest&);
|
||||
RpcResponse GetMediaSourcesList(const RpcRequest&);
|
||||
|
||||
// Category: Outputs
|
||||
RpcResponse ListOutputs(const RpcRequest&);
|
||||
RpcResponse GetOutputInfo(const RpcRequest&);
|
||||
RpcResponse StartOutput(const RpcRequest&);
|
||||
RpcResponse StopOutput(const RpcRequest&);
|
||||
|
||||
// Category: Profiles
|
||||
RpcResponse SetCurrentProfile(const RpcRequest&);
|
||||
RpcResponse GetCurrentProfile(const RpcRequest&);
|
||||
RpcResponse ListProfiles(const RpcRequest&);
|
||||
|
||||
// Category: Recording
|
||||
RpcResponse GetRecordingStatus(const RpcRequest&);
|
||||
RpcResponse StartStopRecording(const RpcRequest&);
|
||||
RpcResponse StartRecording(const RpcRequest&);
|
||||
RpcResponse StopRecording(const RpcRequest&);
|
||||
RpcResponse PauseRecording(const RpcRequest&);
|
||||
RpcResponse ResumeRecording(const RpcRequest&);
|
||||
RpcResponse SetRecordingFolder(const RpcRequest&);
|
||||
RpcResponse GetRecordingFolder(const RpcRequest&);
|
||||
|
||||
// Category: Replay Buffer
|
||||
RpcResponse GetReplayBufferStatus(const RpcRequest&);
|
||||
RpcResponse StartStopReplayBuffer(const RpcRequest&);
|
||||
RpcResponse StartReplayBuffer(const RpcRequest&);
|
||||
RpcResponse StopReplayBuffer(const RpcRequest&);
|
||||
RpcResponse SaveReplayBuffer(const RpcRequest&);
|
||||
|
||||
// Category: Scene Collections
|
||||
RpcResponse SetCurrentSceneCollection(const RpcRequest&);
|
||||
RpcResponse GetCurrentSceneCollection(const RpcRequest&);
|
||||
RpcResponse ListSceneCollections(const RpcRequest&);
|
||||
|
||||
// Category: Scene Items
|
||||
RpcResponse GetSceneItemList(const RpcRequest&);
|
||||
RpcResponse GetSceneItemProperties(const RpcRequest&);
|
||||
RpcResponse SetSceneItemProperties(const RpcRequest&);
|
||||
RpcResponse ResetSceneItem(const RpcRequest&);
|
||||
RpcResponse SetSceneItemRender(const RpcRequest&);
|
||||
RpcResponse SetSceneItemPosition(const RpcRequest&);
|
||||
RpcResponse SetSceneItemTransform(const RpcRequest&);
|
||||
RpcResponse SetSceneItemCrop(const RpcRequest&);
|
||||
RpcResponse DeleteSceneItem(const RpcRequest&);
|
||||
RpcResponse AddSceneItem(const RpcRequest&);
|
||||
RpcResponse DuplicateSceneItem(const RpcRequest&);
|
||||
|
||||
// Category: Scenes
|
||||
RpcResponse SetCurrentScene(const RpcRequest&);
|
||||
RpcResponse GetCurrentScene(const RpcRequest&);
|
||||
RpcResponse GetSceneList(const RpcRequest&);
|
||||
RpcResponse CreateScene(const RpcRequest&);
|
||||
RpcResponse ReorderSceneItems(const RpcRequest&);
|
||||
RpcResponse SetSceneTransitionOverride(const RpcRequest&);
|
||||
RpcResponse RemoveSceneTransitionOverride(const RpcRequest&);
|
||||
RpcResponse GetSceneTransitionOverride(const RpcRequest&);
|
||||
|
||||
// Category: Sources
|
||||
RpcResponse CreateSource(const RpcRequest&);
|
||||
RpcResponse GetSourcesList(const RpcRequest&);
|
||||
RpcResponse GetSourceTypesList(const RpcRequest&);
|
||||
RpcResponse GetVolume(const RpcRequest&);
|
||||
RpcResponse SetVolume(const RpcRequest&);
|
||||
RpcResponse GetMute(const RpcRequest&);
|
||||
RpcResponse SetMute(const RpcRequest&);
|
||||
RpcResponse ToggleMute(const RpcRequest&);
|
||||
RpcResponse GetAudioActive(const RpcRequest&);
|
||||
RpcResponse SetSourceName(const RpcRequest&);
|
||||
RpcResponse SetSyncOffset(const RpcRequest&);
|
||||
RpcResponse GetSyncOffset(const RpcRequest&);
|
||||
RpcResponse GetSourceSettings(const RpcRequest&);
|
||||
RpcResponse SetSourceSettings(const RpcRequest&);
|
||||
RpcResponse GetTextGDIPlusProperties(const RpcRequest&);
|
||||
RpcResponse SetTextGDIPlusProperties(const RpcRequest&);
|
||||
RpcResponse GetTextFreetype2Properties(const RpcRequest&);
|
||||
RpcResponse SetTextFreetype2Properties(const RpcRequest&);
|
||||
RpcResponse GetBrowserSourceProperties(const RpcRequest&);
|
||||
RpcResponse SetBrowserSourceProperties(const RpcRequest&);
|
||||
RpcResponse GetSpecialSources(const RpcRequest&);
|
||||
RpcResponse GetSourceFilters(const RpcRequest&);
|
||||
RpcResponse GetSourceFilterInfo(const RpcRequest&);
|
||||
RpcResponse AddFilterToSource(const RpcRequest&);
|
||||
RpcResponse RemoveFilterFromSource(const RpcRequest&);
|
||||
RpcResponse ReorderSourceFilter(const RpcRequest&);
|
||||
RpcResponse MoveSourceFilter(const RpcRequest&);
|
||||
RpcResponse SetSourceFilterSettings(const RpcRequest&);
|
||||
RpcResponse SetSourceFilterVisibility(const RpcRequest&);
|
||||
RpcResponse GetAudioMonitorType(const RpcRequest&);
|
||||
RpcResponse SetAudioMonitorType(const RpcRequest&);
|
||||
RpcResponse GetSourceDefaultSettings(const RpcRequest&);
|
||||
RpcResponse TakeSourceScreenshot(const RpcRequest&);
|
||||
RpcResponse RefreshBrowserSource(const RpcRequest&);
|
||||
|
||||
// Category: Streaming
|
||||
RpcResponse GetStreamingStatus(const RpcRequest&);
|
||||
RpcResponse StartStopStreaming(const RpcRequest&);
|
||||
RpcResponse StartStreaming(const RpcRequest&);
|
||||
RpcResponse StopStreaming(const RpcRequest&);
|
||||
RpcResponse SetStreamSettings(const RpcRequest&);
|
||||
RpcResponse GetStreamSettings(const RpcRequest&);
|
||||
RpcResponse SaveStreamSettings(const RpcRequest&);
|
||||
RpcResponse SendCaptions(const RpcRequest&);
|
||||
|
||||
// Category: Studio Mode
|
||||
RpcResponse GetStudioModeStatus(const RpcRequest&);
|
||||
RpcResponse GetPreviewScene(const RpcRequest&);
|
||||
RpcResponse SetPreviewScene(const RpcRequest&);
|
||||
RpcResponse TransitionToProgram(const RpcRequest&);
|
||||
RpcResponse EnableStudioMode(const RpcRequest&);
|
||||
RpcResponse DisableStudioMode(const RpcRequest&);
|
||||
RpcResponse ToggleStudioMode(const RpcRequest&);
|
||||
|
||||
// Category: Transitions
|
||||
RpcResponse GetTransitionList(const RpcRequest&);
|
||||
RpcResponse GetCurrentTransition(const RpcRequest&);
|
||||
RpcResponse SetCurrentTransition(const RpcRequest&);
|
||||
RpcResponse SetTransitionDuration(const RpcRequest&);
|
||||
RpcResponse GetTransitionDuration(const RpcRequest&);
|
||||
RpcResponse GetTransitionPosition(const RpcRequest&);
|
||||
RpcResponse GetTransitionSettings(const RpcRequest&);
|
||||
RpcResponse SetTransitionSettings(const RpcRequest&);
|
||||
RpcResponse ReleaseTBar(const RpcRequest&);
|
||||
RpcResponse SetTBarPosition(const RpcRequest&);
|
||||
};
|
475
src/WSRequestHandler_General.cpp
Normal file
475
src/WSRequestHandler_General.cpp
Normal file
@ -0,0 +1,475 @@
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtGui/QImageWriter>
|
||||
|
||||
#include "obs-websocket.h"
|
||||
#include "Config.h"
|
||||
#include "Utils.h"
|
||||
#include "WSEvents.h"
|
||||
#include "protocol/OBSRemoteProtocol.h"
|
||||
|
||||
#define CASE(x) case x: return #x;
|
||||
const char *describe_output_format(int format) {
|
||||
switch (format) {
|
||||
default:
|
||||
CASE(VIDEO_FORMAT_NONE)
|
||||
CASE(VIDEO_FORMAT_I420)
|
||||
CASE(VIDEO_FORMAT_NV12)
|
||||
CASE(VIDEO_FORMAT_YVYU)
|
||||
CASE(VIDEO_FORMAT_YUY2)
|
||||
CASE(VIDEO_FORMAT_UYVY)
|
||||
CASE(VIDEO_FORMAT_RGBA)
|
||||
CASE(VIDEO_FORMAT_BGRA)
|
||||
CASE(VIDEO_FORMAT_BGRX)
|
||||
CASE(VIDEO_FORMAT_Y800)
|
||||
CASE(VIDEO_FORMAT_I444)
|
||||
}
|
||||
}
|
||||
|
||||
const char *describe_color_space(int cs) {
|
||||
switch (cs) {
|
||||
default:
|
||||
CASE(VIDEO_CS_DEFAULT)
|
||||
CASE(VIDEO_CS_601)
|
||||
CASE(VIDEO_CS_709)
|
||||
}
|
||||
}
|
||||
|
||||
const char *describe_color_range(int range) {
|
||||
switch (range) {
|
||||
default:
|
||||
CASE(VIDEO_RANGE_DEFAULT)
|
||||
CASE(VIDEO_RANGE_PARTIAL)
|
||||
CASE(VIDEO_RANGE_FULL)
|
||||
}
|
||||
}
|
||||
|
||||
const char *describe_scale_type(int scale) {
|
||||
switch (scale) {
|
||||
default:
|
||||
CASE(VIDEO_SCALE_DEFAULT)
|
||||
CASE(VIDEO_SCALE_POINT)
|
||||
CASE(VIDEO_SCALE_FAST_BILINEAR)
|
||||
CASE(VIDEO_SCALE_BILINEAR)
|
||||
CASE(VIDEO_SCALE_BICUBIC)
|
||||
}
|
||||
}
|
||||
#undef CASE
|
||||
|
||||
/**
|
||||
* Returns the latest version of the plugin and the API.
|
||||
*
|
||||
* @return {double} `version` OBSRemote compatible API version. Fixed to 1.1 for retrocompatibility.
|
||||
* @return {String} `obs-websocket-version` obs-websocket plugin version.
|
||||
* @return {String} `obs-studio-version` OBS Studio program version.
|
||||
* @return {String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3").
|
||||
* @return {String} `supported-image-export-formats` List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string
|
||||
*
|
||||
* @api requests
|
||||
* @name GetVersion
|
||||
* @category general
|
||||
* @since 0.3
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetVersion(const RpcRequest& request) {
|
||||
QString obsVersion = Utils::OBSVersionString();
|
||||
|
||||
QList<QString> names = messageMap.keys();
|
||||
QList<QByteArray> imageWriterFormats = QImageWriter::supportedImageFormats();
|
||||
|
||||
// (Palakis) OBS' data arrays only support object arrays, so I improvised.
|
||||
QString requests;
|
||||
names.sort(Qt::CaseInsensitive);
|
||||
requests += names.takeFirst();
|
||||
for (const QString& reqName : names) {
|
||||
requests += ("," + reqName);
|
||||
}
|
||||
|
||||
QString supportedImageExportFormats;
|
||||
supportedImageExportFormats += QString::fromUtf8(imageWriterFormats.takeFirst());
|
||||
for (const QByteArray& format : imageWriterFormats) {
|
||||
supportedImageExportFormats += ("," + QString::fromUtf8(format));
|
||||
}
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_double(data, "version", 1.1);
|
||||
obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION);
|
||||
obs_data_set_string(data, "obs-studio-version", obsVersion.toUtf8());
|
||||
obs_data_set_string(data, "available-requests", requests.toUtf8());
|
||||
obs_data_set_string(data, "supported-image-export-formats", supportedImageExportFormats.toUtf8());
|
||||
|
||||
return request.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the client if authentication is required. If so, returns authentication parameters `challenge`
|
||||
* and `salt` (see "Authentication" for more information).
|
||||
*
|
||||
* @return {boolean} `authRequired` Indicates whether authentication is required.
|
||||
* @return {String (optional)} `challenge`
|
||||
* @return {String (optional)} `salt`
|
||||
*
|
||||
* @api requests
|
||||
* @name GetAuthRequired
|
||||
* @category general
|
||||
* @since 0.3
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetAuthRequired(const RpcRequest& request) {
|
||||
bool authRequired = GetConfig()->AuthRequired;
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_bool(data, "authRequired", authRequired);
|
||||
|
||||
if (authRequired) {
|
||||
auto config = GetConfig();
|
||||
obs_data_set_string(data, "challenge",
|
||||
config->SessionChallenge.toUtf8());
|
||||
obs_data_set_string(data, "salt",
|
||||
config->Salt.toUtf8());
|
||||
}
|
||||
|
||||
return request.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to authenticate the client to the server.
|
||||
*
|
||||
* @param {String} `auth` Response to the auth challenge (see "Authentication" for more information).
|
||||
*
|
||||
* @api requests
|
||||
* @name Authenticate
|
||||
* @category general
|
||||
* @since 0.3
|
||||
*/
|
||||
RpcResponse WSRequestHandler::Authenticate(const RpcRequest& request) {
|
||||
if (!request.hasField("auth")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
if (_connProperties.isAuthenticated()) {
|
||||
return request.failed("already authenticated");
|
||||
}
|
||||
|
||||
QString auth = obs_data_get_string(request.parameters(), "auth");
|
||||
if (auth.isEmpty()) {
|
||||
return request.failed("auth not specified!");
|
||||
}
|
||||
|
||||
if (GetConfig()->CheckAuth(auth) == false) {
|
||||
return request.failed("Authentication Failed.");
|
||||
}
|
||||
|
||||
_connProperties.setAuthenticated(true);
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable sending of the Heartbeat event
|
||||
*
|
||||
* @param {boolean} `enable` Starts/Stops emitting heartbeat messages
|
||||
*
|
||||
* @api requests
|
||||
* @name SetHeartbeat
|
||||
* @category general
|
||||
* @since 4.3.0
|
||||
* @deprecated Since 4.9.0. Please poll the appropriate data using requests. Will be removed in v5.0.0.
|
||||
*/
|
||||
RpcResponse WSRequestHandler::SetHeartbeat(const RpcRequest& request) {
|
||||
if (!request.hasField("enable")) {
|
||||
return request.failed("Heartbeat <enable> parameter missing");
|
||||
}
|
||||
|
||||
auto events = GetEventsSystem();
|
||||
events->HeartbeatIsActive = obs_data_get_bool(request.parameters(), "enable");
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_bool(response, "enable", events->HeartbeatIsActive);
|
||||
|
||||
return request.success(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the filename formatting string
|
||||
*
|
||||
* @param {String} `filename-formatting` Filename formatting string to set.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetFilenameFormatting
|
||||
* @category general
|
||||
* @since 4.3.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::SetFilenameFormatting(const RpcRequest& request) {
|
||||
if (!request.hasField("filename-formatting")) {
|
||||
return request.failed("<filename-formatting> parameter missing");
|
||||
}
|
||||
|
||||
QString filenameFormatting = obs_data_get_string(request.parameters(), "filename-formatting");
|
||||
if (filenameFormatting.isEmpty()) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
Utils::SetFilenameFormatting(filenameFormatting.toUtf8());
|
||||
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filename formatting string
|
||||
*
|
||||
* @return {String} `filename-formatting` Current filename formatting string.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetFilenameFormatting
|
||||
* @category general
|
||||
* @since 4.3.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetFilenameFormatting(const RpcRequest& request) {
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "filename-formatting", Utils::GetFilenameFormatting());
|
||||
|
||||
return request.success(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OBS stats (almost the same info as provided in OBS' stats window)
|
||||
*
|
||||
* @return {OBSStats} `stats` [OBS stats](#obsstats)
|
||||
*
|
||||
* @api requests
|
||||
* @name GetStats
|
||||
* @category general
|
||||
* @since 4.6.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetStats(const RpcRequest& request) {
|
||||
OBSDataAutoRelease stats = GetEventsSystem()->GetStats();
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_obj(response, "stats", stats);
|
||||
|
||||
return request.success(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast custom message to all connected WebSocket clients
|
||||
*
|
||||
* @param {String} `realm` Identifier to be choosen by the client
|
||||
* @param {Object} `data` User-defined data
|
||||
*
|
||||
* @api requests
|
||||
* @name BroadcastCustomMessage
|
||||
* @category general
|
||||
* @since 4.7.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::BroadcastCustomMessage(const RpcRequest& request) {
|
||||
if (!request.hasField("realm") || !request.hasField("data")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
QString realm = obs_data_get_string(request.parameters(), "realm");
|
||||
OBSDataAutoRelease data = obs_data_get_obj(request.parameters(), "data");
|
||||
|
||||
if (realm.isEmpty()) {
|
||||
return request.failed("realm not specified!");
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return request.failed("data not specified!");
|
||||
}
|
||||
|
||||
auto events = GetEventsSystem();
|
||||
events->OnBroadcastCustomMessage(realm, data);
|
||||
|
||||
return request.success();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get basic OBS video information
|
||||
*
|
||||
* @return {int} `baseWidth` Base (canvas) width
|
||||
* @return {int} `baseHeight` Base (canvas) height
|
||||
* @return {int} `outputWidth` Output width
|
||||
* @return {int} `outputHeight` Output height
|
||||
* @return {String} `scaleType` Scaling method used if output size differs from base size
|
||||
* @return {double} `fps` Frames rendered per second
|
||||
* @return {String} `videoFormat` Video color format
|
||||
* @return {String} `colorSpace` Color space for YUV
|
||||
* @return {String} `colorRange` Color range (full or partial)
|
||||
*
|
||||
* @api requests
|
||||
* @name GetVideoInfo
|
||||
* @category general
|
||||
* @since 4.6.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetVideoInfo(const RpcRequest& request) {
|
||||
obs_video_info ovi;
|
||||
obs_get_video_info(&ovi);
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_int(response, "baseWidth", ovi.base_width);
|
||||
obs_data_set_int(response, "baseHeight", ovi.base_height);
|
||||
obs_data_set_int(response, "outputWidth", ovi.output_width);
|
||||
obs_data_set_int(response, "outputHeight", ovi.output_height);
|
||||
obs_data_set_double(response, "fps", (double)ovi.fps_num / ovi.fps_den);
|
||||
obs_data_set_string(response, "videoFormat", describe_output_format(ovi.output_format));
|
||||
obs_data_set_string(response, "colorSpace", describe_color_space(ovi.colorspace));
|
||||
obs_data_set_string(response, "colorRange", describe_color_range(ovi.range));
|
||||
obs_data_set_string(response, "scaleType", describe_scale_type(ovi.scale_type));
|
||||
|
||||
return request.success(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer.
|
||||
*
|
||||
* @param {String (Optional)} `type` Type of projector: `Preview` (default), `Source`, `Scene`, `StudioProgram`, or `Multiview` (case insensitive).
|
||||
* @param {int (Optional)} `monitor` Monitor to open the projector on. If -1 or omitted, opens a window.
|
||||
* @param {String (Optional)} `geometry` Size and position of the projector window (only if monitor is -1). Encoded in Base64 using [Qt's geometry encoding](https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors.
|
||||
* @param {String (Optional)} `name` Name of the source or scene to be displayed (ignored for other projector types).
|
||||
*
|
||||
* @api requests
|
||||
* @name OpenProjector
|
||||
* @category general
|
||||
* @since 4.8.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::OpenProjector(const RpcRequest& request) {
|
||||
const char* type = obs_data_get_string(request.parameters(), "type");
|
||||
|
||||
int monitor = -1;
|
||||
if (request.hasField("monitor")) {
|
||||
monitor = obs_data_get_int(request.parameters(), "monitor");
|
||||
}
|
||||
|
||||
const char* geometry = obs_data_get_string(request.parameters(), "geometry");
|
||||
const char* name = obs_data_get_string(request.parameters(), "name");
|
||||
|
||||
obs_frontend_open_projector(type, monitor, geometry, name);
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes hotkey routine, identified by hotkey unique name
|
||||
*
|
||||
* @param {String} `hotkeyName` Unique name of the hotkey, as defined when registering the hotkey (e.g. "ReplayBuffer.Save")
|
||||
*
|
||||
* @api requests
|
||||
* @name TriggerHotkeyByName
|
||||
* @category general
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::TriggerHotkeyByName(const RpcRequest& request) {
|
||||
const char* name = obs_data_get_string(request.parameters(), "hotkeyName");
|
||||
|
||||
obs_hotkey_t* hk = Utils::FindHotkeyByName(name);
|
||||
if (!hk) {
|
||||
return request.failed("hotkey not found");
|
||||
}
|
||||
obs_hotkey_trigger_routed_callback(obs_hotkey_get_id(hk), true);
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes hotkey routine, identified by bound combination of keys. A single key combination might trigger multiple hotkey routines depending on user settings
|
||||
*
|
||||
* @param {String} `keyId` Main key identifier (e.g. `OBS_KEY_A` for key "A"). Available identifiers [here](https://github.com/obsproject/obs-studio/blob/master/libobs/obs-hotkeys.h)
|
||||
* @param {Object (Optional)} `keyModifiers` Optional key modifiers object. False entries can be ommitted
|
||||
* @param {boolean} `keyModifiers.shift` Trigger Shift Key
|
||||
* @param {boolean} `keyModifiers.alt` Trigger Alt Key
|
||||
* @param {boolean} `keyModifiers.control` Trigger Control (Ctrl) Key
|
||||
* @param {boolean} `keyModifiers.command` Trigger Command Key (Mac)
|
||||
*
|
||||
* @api requests
|
||||
* @name TriggerHotkeyBySequence
|
||||
* @category general
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::TriggerHotkeyBySequence(const RpcRequest& request) {
|
||||
if (!request.hasField("keyId")) {
|
||||
return request.failed("missing request keyId parameter");
|
||||
}
|
||||
|
||||
OBSDataAutoRelease data = obs_data_get_obj(request.parameters(), "keyModifiers");
|
||||
|
||||
obs_key_combination_t combo = {0};
|
||||
uint32_t modifiers = 0;
|
||||
if (obs_data_get_bool(data, "shift"))
|
||||
modifiers |= INTERACT_SHIFT_KEY;
|
||||
if (obs_data_get_bool(data, "control"))
|
||||
modifiers |= INTERACT_CONTROL_KEY;
|
||||
if (obs_data_get_bool(data, "alt"))
|
||||
modifiers |= INTERACT_ALT_KEY;
|
||||
if (obs_data_get_bool(data, "command"))
|
||||
modifiers |= INTERACT_COMMAND_KEY;
|
||||
|
||||
combo.modifiers = modifiers;
|
||||
combo.key = obs_key_from_name(obs_data_get_string(request.parameters(), "keyId"));
|
||||
|
||||
if (!modifiers
|
||||
&& (combo.key == OBS_KEY_NONE || combo.key >= OBS_KEY_LAST_VALUE)) {
|
||||
return request.failed("invalid key-modifier combination");
|
||||
}
|
||||
|
||||
// Inject hotkey press-release sequence
|
||||
obs_hotkey_inject_event(combo, false);
|
||||
obs_hotkey_inject_event(combo, true);
|
||||
obs_hotkey_inject_event(combo, false);
|
||||
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a list of requests sequentially (one-by-one on the same thread).
|
||||
*
|
||||
* @param {Array<Object>} `requests` Array of requests to perform. Executed in order.
|
||||
* @param {String} `requests.*.request-type` Request type. Eg. `GetVersion`.
|
||||
* @param {String (Optional)} `requests.*.message-id` ID of the individual request. Can be any string and not required to be unique. Defaults to empty string if not specified.
|
||||
* @param {boolean (Optional)} `abortOnFail` Stop processing batch requests if one returns a failure.
|
||||
*
|
||||
* @return {Array<Object>} `results` Batch requests results, ordered sequentially.
|
||||
* @return {String} `results.*.message-id` ID of the individual request which was originally provided by the client.
|
||||
* @return {String} `results.*.status` Status response as string. Either `ok` or `error`.
|
||||
* @return {String (Optional)} `results.*.error` Error message accompanying an `error` status.
|
||||
*
|
||||
* @api requests
|
||||
* @name ExecuteBatch
|
||||
* @category general
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::ExecuteBatch(const RpcRequest& request) {
|
||||
if (!request.hasField("requests")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
bool abortOnFail = obs_data_get_bool(request.parameters(), "abortOnFail");
|
||||
|
||||
OBSDataArrayAutoRelease results = obs_data_array_create();
|
||||
|
||||
OBSDataArrayAutoRelease requests = obs_data_get_array(request.parameters(), "requests");
|
||||
size_t requestsCount = obs_data_array_count(requests);
|
||||
for (size_t i = 0; i < requestsCount; i++) {
|
||||
OBSDataAutoRelease requestData = obs_data_array_item(requests, i);
|
||||
QString messageId = obs_data_get_string(requestData, "message-id");
|
||||
QString methodName = obs_data_get_string(requestData, "request-type");
|
||||
obs_data_unset_user_value(requestData, "request-type");
|
||||
obs_data_unset_user_value(requestData, "message-id");
|
||||
|
||||
// build RpcRequest from json data object
|
||||
RpcRequest subRequest(messageId, methodName, requestData);
|
||||
|
||||
// execute the request
|
||||
RpcResponse subResponse = processRequest(subRequest);
|
||||
|
||||
// transform response into json data
|
||||
OBSDataAutoRelease subResponseData = OBSRemoteProtocol::rpcResponseToJsonData(subResponse);
|
||||
|
||||
obs_data_array_push_back(results, subResponseData);
|
||||
|
||||
// if told to abort on fail and a failure occurs, stop request processing and return the progress
|
||||
if (abortOnFail && (subResponse.status() == RpcResponse::Status::Error))
|
||||
break;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_array(response, "results", results);
|
||||
return request.success(response);
|
||||
}
|
396
src/WSRequestHandler_MediaControl.cpp
Normal file
396
src/WSRequestHandler_MediaControl.cpp
Normal file
@ -0,0 +1,396 @@
|
||||
#include "Utils.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
bool isMediaSource(const QString& sourceKind)
|
||||
{
|
||||
return (sourceKind == "vlc_source" || sourceKind == "ffmpeg_source");
|
||||
}
|
||||
|
||||
QString getSourceMediaState(obs_source_t *source)
|
||||
{
|
||||
QString mediaState;
|
||||
enum obs_media_state mstate = obs_source_media_get_state(source);
|
||||
switch (mstate) {
|
||||
case OBS_MEDIA_STATE_NONE:
|
||||
mediaState = "none";
|
||||
break;
|
||||
case OBS_MEDIA_STATE_PLAYING:
|
||||
mediaState = "playing";
|
||||
break;
|
||||
case OBS_MEDIA_STATE_OPENING:
|
||||
mediaState = "opening";
|
||||
break;
|
||||
case OBS_MEDIA_STATE_BUFFERING:
|
||||
mediaState = "buffering";
|
||||
break;
|
||||
case OBS_MEDIA_STATE_PAUSED:
|
||||
mediaState = "paused";
|
||||
break;
|
||||
case OBS_MEDIA_STATE_STOPPED:
|
||||
mediaState = "stopped";
|
||||
break;
|
||||
case OBS_MEDIA_STATE_ENDED:
|
||||
mediaState = "ended";
|
||||
break;
|
||||
case OBS_MEDIA_STATE_ERROR:
|
||||
mediaState = "error";
|
||||
break;
|
||||
default:
|
||||
mediaState = "unknown";
|
||||
}
|
||||
return mediaState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause or play a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)
|
||||
*
|
||||
* @param {String} `sourceName` Source name.
|
||||
* @param {boolean} `playPause` Whether to pause or play the source. `false` for play, `true` for pause.
|
||||
*
|
||||
* @api requests
|
||||
* @name PlayPauseMedia
|
||||
* @category media control
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::PlayPauseMedia(const RpcRequest& request) {
|
||||
if ((!request.hasField("sourceName")) || (!request.hasField("playPause"))) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
QString sourceName = obs_data_get_string(request.parameters(), "sourceName");
|
||||
bool playPause = obs_data_get_bool(request.parameters(), "playPause");
|
||||
if (sourceName.isEmpty()) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8());
|
||||
if (!source) {
|
||||
return request.failed("specified source doesn't exist");
|
||||
}
|
||||
|
||||
obs_source_media_play_pause(source, playPause);
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)
|
||||
*
|
||||
* @param {String} `sourceName` Source name.
|
||||
*
|
||||
* @api requests
|
||||
* @name RestartMedia
|
||||
* @category media control
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::RestartMedia(const RpcRequest& request) {
|
||||
if (!request.hasField("sourceName")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
QString sourceName = obs_data_get_string(request.parameters(), "sourceName");
|
||||
if (sourceName.isEmpty()) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8());
|
||||
if (!source) {
|
||||
return request.failed("specified source doesn't exist");
|
||||
}
|
||||
|
||||
obs_source_media_restart(source);
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)
|
||||
*
|
||||
* @param {String} `sourceName` Source name.
|
||||
*
|
||||
* @api requests
|
||||
* @name StopMedia
|
||||
* @category media control
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::StopMedia(const RpcRequest& request) {
|
||||
if (!request.hasField("sourceName")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
QString sourceName = obs_data_get_string(request.parameters(), "sourceName");
|
||||
if (sourceName.isEmpty()) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8());
|
||||
if (!source) {
|
||||
return request.failed("specified source doesn't exist");
|
||||
}
|
||||
|
||||
obs_source_media_stop(source);
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip to the next media item in the playlist. Supports only vlc media source (as of OBS v25.0.8)
|
||||
*
|
||||
* @param {String} `sourceName` Source name.
|
||||
*
|
||||
* @api requests
|
||||
* @name NextMedia
|
||||
* @category media control
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::NextMedia(const RpcRequest& request) {
|
||||
if (!request.hasField("sourceName")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
QString sourceName = obs_data_get_string(request.parameters(), "sourceName");
|
||||
if (sourceName.isEmpty()) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8());
|
||||
if (!source) {
|
||||
return request.failed("specified source doesn't exist");
|
||||
}
|
||||
|
||||
obs_source_media_next(source);
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the previous media item in the playlist. Supports only vlc media source (as of OBS v25.0.8)
|
||||
*
|
||||
* @param {String} `sourceName` Source name.
|
||||
*
|
||||
* @api requests
|
||||
* @name PreviousMedia
|
||||
* @category media control
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::PreviousMedia(const RpcRequest& request) {
|
||||
if (!request.hasField("sourceName")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
QString sourceName = obs_data_get_string(request.parameters(), "sourceName");
|
||||
if (sourceName.isEmpty()) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8());
|
||||
if (!source) {
|
||||
return request.failed("specified source doesn't exist");
|
||||
}
|
||||
|
||||
obs_source_media_previous(source);
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of media in milliseconds. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)
|
||||
* Note: For some reason, for the first 5 or so seconds that the media is playing, the total duration can be off by upwards of 50ms.
|
||||
*
|
||||
* @param {String} `sourceName` Source name.
|
||||
*
|
||||
* @return {int} `mediaDuration` The total length of media in milliseconds..
|
||||
*
|
||||
* @api requests
|
||||
* @name GetMediaDuration
|
||||
* @category media control
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetMediaDuration(const RpcRequest& request) {
|
||||
if (!request.hasField("sourceName")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
QString sourceName = obs_data_get_string(request.parameters(), "sourceName");
|
||||
if (sourceName.isEmpty()) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8());
|
||||
if (!source) {
|
||||
return request.failed("specified source doesn't exist");
|
||||
}
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_int(response, "mediaDuration", obs_source_media_get_duration(source));
|
||||
return request.success(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current timestamp of media in milliseconds. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)
|
||||
*
|
||||
* @param {String} `sourceName` Source name.
|
||||
*
|
||||
* @return {int} `timestamp` The time in milliseconds since the start of the media.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetMediaTime
|
||||
* @category media control
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetMediaTime(const RpcRequest& request) {
|
||||
if (!request.hasField("sourceName")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
QString sourceName = obs_data_get_string(request.parameters(), "sourceName");
|
||||
if (sourceName.isEmpty()) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8());
|
||||
if (!source) {
|
||||
return request.failed("specified source doesn't exist");
|
||||
}
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_int(response, "timestamp", obs_source_media_get_time(source));
|
||||
return request.success(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timestamp of a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)
|
||||
*
|
||||
* @param {String} `sourceName` Source name.
|
||||
* @param {int} `timestamp` Milliseconds to set the timestamp to.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetMediaTime
|
||||
* @category media control
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::SetMediaTime(const RpcRequest& request) {
|
||||
if (!request.hasField("sourceName") || !request.hasField("timestamp")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
QString sourceName = obs_data_get_string(request.parameters(), "sourceName");
|
||||
int64_t timestamp = (int64_t)obs_data_get_int(request.parameters(), "timestamp");
|
||||
if (sourceName.isEmpty()) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8());
|
||||
if (!source) {
|
||||
return request.failed("specified source doesn't exist");
|
||||
}
|
||||
|
||||
obs_source_media_set_time(source, timestamp);
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrub media using a supplied offset. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)
|
||||
* Note: Due to processing/network delays, this request is not perfect. The processing rate of this request has also not been tested.
|
||||
*
|
||||
* @param {String} `sourceName` Source name.
|
||||
* @param {int} `timeOffset` Millisecond offset (positive or negative) to offset the current media position.
|
||||
*
|
||||
* @api requests
|
||||
* @name ScrubMedia
|
||||
* @category media control
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::ScrubMedia(const RpcRequest& request) {
|
||||
if (!request.hasField("sourceName") || !request.hasField("timeOffset")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
QString sourceName = obs_data_get_string(request.parameters(), "sourceName");
|
||||
int64_t timeOffset = (int64_t)obs_data_get_int(request.parameters(), "timeOffset");
|
||||
if (sourceName.isEmpty()) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8());
|
||||
if (!source) {
|
||||
return request.failed("specified source doesn't exist");
|
||||
}
|
||||
|
||||
int64_t newTime = obs_source_media_get_time(source) + timeOffset;
|
||||
if (newTime < 0) {
|
||||
newTime = 0;
|
||||
}
|
||||
|
||||
obs_source_media_set_time(source, newTime);
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current playing state of a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)
|
||||
*
|
||||
* @param {String} `sourceName` Source name.
|
||||
*
|
||||
* @return {String} `mediaState` The media state of the provided source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown`
|
||||
*
|
||||
* @api requests
|
||||
* @name GetMediaState
|
||||
* @category media control
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetMediaState(const RpcRequest& request) {
|
||||
if (!request.hasField("sourceName")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
QString sourceName = obs_data_get_string(request.parameters(), "sourceName");
|
||||
if (sourceName.isEmpty()) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8());
|
||||
if (!source) {
|
||||
return request.failed("specified source doesn't exist");
|
||||
}
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "mediaState", getSourceMediaState(source).toUtf8());
|
||||
|
||||
return request.success(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* List the media state of all media sources (vlc and media source)
|
||||
*
|
||||
* @return {Array<Object>} `mediaSources` Array of sources
|
||||
* @return {String} `mediaSources.*.sourceName` Unique source name
|
||||
* @return {String} `mediaSources.*.sourceKind` Unique source internal type (a.k.a `ffmpeg_source` or `vlc_source`)
|
||||
* @return {String} `mediaSources.*.mediaState` The current state of media for that source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown`
|
||||
*
|
||||
* @api requests
|
||||
* @name GetMediaSourcesList
|
||||
* @category sources
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetMediaSourcesList(const RpcRequest& request)
|
||||
{
|
||||
OBSDataArrayAutoRelease sourcesArray = obs_data_array_create();
|
||||
|
||||
auto sourceEnumProc = [](void* privateData, obs_source_t* source) -> bool {
|
||||
obs_data_array_t* sourcesArray = (obs_data_array_t*)privateData;
|
||||
|
||||
QString sourceKind = obs_source_get_id(source);
|
||||
if (isMediaSource(sourceKind)) {
|
||||
OBSDataAutoRelease sourceData = obs_data_create();
|
||||
obs_data_set_string(sourceData, "sourceName", obs_source_get_name(source));
|
||||
obs_data_set_string(sourceData, "sourceKind", sourceKind.toUtf8());
|
||||
|
||||
QString mediaState = getSourceMediaState(source);
|
||||
obs_data_set_string(sourceData, "mediaState", mediaState.toUtf8());
|
||||
|
||||
obs_data_array_push_back(sourcesArray, sourceData);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
obs_enum_sources(sourceEnumProc, sourcesArray);
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_array(response, "mediaSources", sourcesArray);
|
||||
return request.success(response);
|
||||
}
|
188
src/WSRequestHandler_Outputs.cpp
Normal file
188
src/WSRequestHandler_Outputs.cpp
Normal file
@ -0,0 +1,188 @@
|
||||
#include <functional>
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
/**
|
||||
* @typedef {Object} `Output`
|
||||
* @property {String} `name` Output name
|
||||
* @property {String} `type` Output type/kind
|
||||
* @property {int} `width` Video output width
|
||||
* @property {int} `height` Video output height
|
||||
* @property {Object} `flags` Output flags
|
||||
* @property {int} `flags.rawValue` Raw flags value
|
||||
* @property {boolean} `flags.audio` Output uses audio
|
||||
* @property {boolean} `flags.video` Output uses video
|
||||
* @property {boolean} `flags.encoded` Output is encoded
|
||||
* @property {boolean} `flags.multiTrack` Output uses several audio tracks
|
||||
* @property {boolean} `flags.service` Output uses a service
|
||||
* @property {Object} `settings` Output name
|
||||
* @property {boolean} `active` Output status (active or not)
|
||||
* @property {boolean} `reconnecting` Output reconnection status (reconnecting or not)
|
||||
* @property {double} `congestion` Output congestion
|
||||
* @property {int} `totalFrames` Number of frames sent
|
||||
* @property {int} `droppedFrames` Number of frames dropped
|
||||
* @property {int} `totalBytes` Total bytes sent
|
||||
*/
|
||||
obs_data_t* getOutputInfo(obs_output_t* output)
|
||||
{
|
||||
if (!output) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease settings = obs_output_get_settings(output);
|
||||
|
||||
uint32_t rawFlags = obs_output_get_flags(output);
|
||||
OBSDataAutoRelease flags = obs_data_create();
|
||||
obs_data_set_int(flags, "rawValue", rawFlags);
|
||||
obs_data_set_bool(flags, "audio", rawFlags & OBS_OUTPUT_AUDIO);
|
||||
obs_data_set_bool(flags, "video", rawFlags & OBS_OUTPUT_VIDEO);
|
||||
obs_data_set_bool(flags, "encoded", rawFlags & OBS_OUTPUT_ENCODED);
|
||||
obs_data_set_bool(flags, "multiTrack", rawFlags & OBS_OUTPUT_MULTI_TRACK);
|
||||
obs_data_set_bool(flags, "service", rawFlags & OBS_OUTPUT_SERVICE);
|
||||
|
||||
obs_data_t* data = obs_data_create();
|
||||
|
||||
obs_data_set_string(data, "name", obs_output_get_name(output));
|
||||
obs_data_set_string(data, "type", obs_output_get_id(output));
|
||||
obs_data_set_int(data, "width", obs_output_get_width(output));
|
||||
obs_data_set_int(data, "height", obs_output_get_height(output));
|
||||
obs_data_set_obj(data, "flags", flags);
|
||||
obs_data_set_obj(data, "settings", settings);
|
||||
|
||||
obs_data_set_bool(data, "active", obs_output_active(output));
|
||||
obs_data_set_bool(data, "reconnecting", obs_output_reconnecting(output));
|
||||
obs_data_set_double(data, "congestion", obs_output_get_congestion(output));
|
||||
obs_data_set_int(data, "totalFrames", obs_output_get_total_frames(output));
|
||||
obs_data_set_int(data, "droppedFrames", obs_output_get_frames_dropped(output));
|
||||
obs_data_set_int(data, "totalBytes", obs_output_get_total_bytes(output));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
RpcResponse findOutputOrFail(const RpcRequest& request, std::function<RpcResponse (obs_output_t*, const RpcRequest&)> callback)
|
||||
{
|
||||
if (!request.hasField("outputName")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
const char* outputName = obs_data_get_string(request.parameters(), "outputName");
|
||||
OBSOutputAutoRelease output = obs_get_output_by_name(outputName);
|
||||
if (!output) {
|
||||
return request.failed("specified output doesn't exist");
|
||||
}
|
||||
|
||||
return callback(output, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* List existing outputs
|
||||
*
|
||||
* @return {Array<Output>} `outputs` Outputs list
|
||||
*
|
||||
* @api requests
|
||||
* @name ListOutputs
|
||||
* @category outputs
|
||||
* @since 4.7.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::ListOutputs(const RpcRequest& request)
|
||||
{
|
||||
OBSDataArrayAutoRelease outputs = obs_data_array_create();
|
||||
|
||||
obs_enum_outputs([](void* param, obs_output_t* output) {
|
||||
obs_data_array_t* outputs = reinterpret_cast<obs_data_array_t*>(param);
|
||||
|
||||
OBSDataAutoRelease outputInfo = getOutputInfo(output);
|
||||
obs_data_array_push_back(outputs, outputInfo);
|
||||
|
||||
return true;
|
||||
}, outputs);
|
||||
|
||||
OBSDataAutoRelease fields = obs_data_create();
|
||||
obs_data_set_array(fields, "outputs", outputs);
|
||||
|
||||
return request.success(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about a single output
|
||||
*
|
||||
* @param {String} `outputName` Output name
|
||||
*
|
||||
* @return {Output} `outputInfo` Output info
|
||||
*
|
||||
* @api requests
|
||||
* @name GetOutputInfo
|
||||
* @category outputs
|
||||
* @since 4.7.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetOutputInfo(const RpcRequest& request)
|
||||
{
|
||||
return findOutputOrFail(request, [](obs_output_t* output, const RpcRequest& request) {
|
||||
OBSDataAutoRelease outputInfo = getOutputInfo(output);
|
||||
|
||||
OBSDataAutoRelease fields = obs_data_create();
|
||||
obs_data_set_obj(fields, "outputInfo", outputInfo);
|
||||
return request.success(fields);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start an output
|
||||
*
|
||||
* Note: Controlling outputs is an experimental feature of obs-websocket. Some plugins which add outputs to OBS may not function properly when they are controlled in this way.
|
||||
*
|
||||
* @param {String} `outputName` Output name
|
||||
*
|
||||
* @api requests
|
||||
* @name StartOutput
|
||||
* @category outputs
|
||||
* @since 4.7.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::StartOutput(const RpcRequest& request)
|
||||
{
|
||||
return findOutputOrFail(request, [](obs_output_t* output, const RpcRequest& request) {
|
||||
if (obs_output_active(output)) {
|
||||
return request.failed("output already active");
|
||||
}
|
||||
|
||||
bool success = obs_output_start(output);
|
||||
if (!success) {
|
||||
QString lastError = obs_output_get_last_error(output);
|
||||
QString errorMessage = QString("output start failed: %1").arg(lastError);
|
||||
return request.failed(errorMessage);
|
||||
}
|
||||
|
||||
return request.success();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop an output
|
||||
*
|
||||
* Note: Controlling outputs is an experimental feature of obs-websocket. Some plugins which add outputs to OBS may not function properly when they are controlled in this way.
|
||||
*
|
||||
* @param {String} `outputName` Output name
|
||||
* @param {boolean (optional)} `force` Force stop (default: false)
|
||||
*
|
||||
* @api requests
|
||||
* @name StopOutput
|
||||
* @category outputs
|
||||
* @since 4.7.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::StopOutput(const RpcRequest& request)
|
||||
{
|
||||
return findOutputOrFail(request, [](obs_output_t* output, const RpcRequest& request) {
|
||||
if (!obs_output_active(output)) {
|
||||
return request.failed("output not active");
|
||||
}
|
||||
|
||||
bool forceStop = obs_data_get_bool(request.parameters(), "force");
|
||||
if (forceStop) {
|
||||
obs_output_force_stop(output);
|
||||
} else {
|
||||
obs_output_stop(output);
|
||||
}
|
||||
|
||||
return request.success();
|
||||
});
|
||||
}
|
77
src/WSRequestHandler_Profiles.cpp
Normal file
77
src/WSRequestHandler_Profiles.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
#include "Utils.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
/**
|
||||
* Set the currently active profile.
|
||||
*
|
||||
* @param {String} `profile-name` Name of the desired profile.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetCurrentProfile
|
||||
* @category profiles
|
||||
* @since 4.0.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::SetCurrentProfile(const RpcRequest& request) {
|
||||
if (!request.hasField("profile-name")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
const char* profileName = obs_data_get_string(request.parameters(), "profile-name");
|
||||
if (!profileName) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
char** profiles = obs_frontend_get_profiles();
|
||||
bool profileExists = Utils::StringInStringList(profiles, profileName);
|
||||
bfree(profiles);
|
||||
if (!profileExists) {
|
||||
return request.failed("profile does not exist");
|
||||
}
|
||||
|
||||
obs_queue_task(OBS_TASK_UI, [](void* param) {
|
||||
obs_frontend_set_current_profile(reinterpret_cast<const char*>(param));
|
||||
}, (void*)profileName, true);
|
||||
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the current profile.
|
||||
*
|
||||
* @return {String} `profile-name` Name of the currently active profile.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetCurrentProfile
|
||||
* @category profiles
|
||||
* @since 4.0.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetCurrentProfile(const RpcRequest& request) {
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
char* currentProfile = obs_frontend_get_current_profile();
|
||||
obs_data_set_string(response, "profile-name", currentProfile);
|
||||
bfree(currentProfile);
|
||||
return request.success(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of available profiles.
|
||||
*
|
||||
* @return {Array<Object>} `profiles` List of available profiles.
|
||||
* @return {String} `profiles.*.profile-name` Filter name
|
||||
*
|
||||
* @api requests
|
||||
* @name ListProfiles
|
||||
* @category profiles
|
||||
* @since 4.0.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::ListProfiles(const RpcRequest& request) {
|
||||
char** profiles = obs_frontend_get_profiles();
|
||||
OBSDataArrayAutoRelease list = Utils::StringListToArray(profiles, "profile-name");
|
||||
bfree(profiles);
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_array(response, "profiles", list);
|
||||
|
||||
return request.success(response);
|
||||
}
|
182
src/WSRequestHandler_Recording.cpp
Normal file
182
src/WSRequestHandler_Recording.cpp
Normal file
@ -0,0 +1,182 @@
|
||||
#include "obs-websocket.h"
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
#include <functional>
|
||||
#include <util/platform.h>
|
||||
#include "Utils.h"
|
||||
#include "WSEvents.h"
|
||||
|
||||
RpcResponse ifCanPause(const RpcRequest& request, std::function<RpcResponse()> callback)
|
||||
{
|
||||
if (!obs_frontend_recording_active()) {
|
||||
return request.failed("recording is not active");
|
||||
}
|
||||
|
||||
return callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current recording status.
|
||||
*
|
||||
* @return {boolean} `isRecording` Current recording status.
|
||||
* @return {boolean} `isRecordingPaused` Whether the recording is paused or not.
|
||||
* @return {String (optional)} `recordTimecode` Time elapsed since recording started (only present if currently recording).
|
||||
* @return {String (optional)} `recordingFilename` Absolute path to the recording file (only present if currently recording).
|
||||
*
|
||||
* @api requests
|
||||
* @name GetRecordingStatus
|
||||
* @category recording
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetRecordingStatus(const RpcRequest& request) {
|
||||
auto events = GetEventsSystem();
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_bool(data, "isRecording", obs_frontend_recording_active());
|
||||
obs_data_set_bool(data, "isRecordingPaused", obs_frontend_recording_paused());
|
||||
|
||||
if (obs_frontend_recording_active()) {
|
||||
QString recordingTimecode = events->getRecordingTimecode();
|
||||
obs_data_set_string(data, "recordTimecode", recordingTimecode.toUtf8().constData());
|
||||
obs_data_set_string(data, "recordingFilename", Utils::GetCurrentRecordingFilename());
|
||||
}
|
||||
|
||||
return request.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle recording on or off (depending on the current recording state).
|
||||
*
|
||||
* @api requests
|
||||
* @name StartStopRecording
|
||||
* @category recording
|
||||
* @since 0.3
|
||||
*/
|
||||
RpcResponse WSRequestHandler::StartStopRecording(const RpcRequest& request) {
|
||||
(obs_frontend_recording_active() ? obs_frontend_recording_stop() : obs_frontend_recording_start());
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start recording.
|
||||
* Will return an `error` if recording is already active.
|
||||
*
|
||||
* @api requests
|
||||
* @name StartRecording
|
||||
* @category recording
|
||||
* @since 4.1.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::StartRecording(const RpcRequest& request) {
|
||||
if (obs_frontend_recording_active()) {
|
||||
return request.failed("recording already active");
|
||||
}
|
||||
|
||||
obs_frontend_recording_start();
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop recording.
|
||||
* Will return an `error` if recording is not active.
|
||||
*
|
||||
* @api requests
|
||||
* @name StopRecording
|
||||
* @category recording
|
||||
* @since 4.1.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::StopRecording(const RpcRequest& request) {
|
||||
if (!obs_frontend_recording_active()) {
|
||||
return request.failed("recording not active");
|
||||
}
|
||||
|
||||
obs_frontend_recording_stop();
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the current recording.
|
||||
* Returns an error if recording is not active or already paused.
|
||||
*
|
||||
* @api requests
|
||||
* @name PauseRecording
|
||||
* @category recording
|
||||
* @since 4.7.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::PauseRecording(const RpcRequest& request) {
|
||||
return ifCanPause(request, [request]() {
|
||||
if (obs_frontend_recording_paused()) {
|
||||
return request.failed("recording already paused");
|
||||
}
|
||||
|
||||
obs_frontend_recording_pause(true);
|
||||
return request.success();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume/unpause the current recording (if paused).
|
||||
* Returns an error if recording is not active or not paused.
|
||||
*
|
||||
* @api requests
|
||||
* @name ResumeRecording
|
||||
* @category recording
|
||||
* @since 4.7.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::ResumeRecording(const RpcRequest& request) {
|
||||
return ifCanPause(request, [request]() {
|
||||
if (!obs_frontend_recording_paused()) {
|
||||
return request.failed("recording is not paused");
|
||||
}
|
||||
|
||||
obs_frontend_recording_pause(false);
|
||||
return request.success();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* In the current profile, sets the recording folder of the Simple and Advanced
|
||||
* output modes to the specified value.
|
||||
*
|
||||
* Note: If `SetRecordingFolder` is called while a recording is
|
||||
* in progress, the change won't be applied immediately and will be
|
||||
* effective on the next recording.
|
||||
*
|
||||
* @param {String} `rec-folder` Path of the recording folder.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetRecordingFolder
|
||||
* @category recording
|
||||
* @since 4.1.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::SetRecordingFolder(const RpcRequest& request) {
|
||||
if (!request.hasField("rec-folder")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
const char* newRecFolder = obs_data_get_string(request.parameters(), "rec-folder");
|
||||
bool success = Utils::SetRecordingFolder(newRecFolder);
|
||||
if (!success) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path of the current recording folder.
|
||||
*
|
||||
* @return {String} `rec-folder` Path of the recording folder.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetRecordingFolder
|
||||
* @category recording
|
||||
* @since 4.1.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetRecordingFolder(const RpcRequest& request) {
|
||||
const char* recFolder = Utils::GetRecordingFolder();
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "rec-folder", recFolder);
|
||||
|
||||
return request.success(response);
|
||||
}
|
108
src/WSRequestHandler_ReplayBuffer.cpp
Normal file
108
src/WSRequestHandler_ReplayBuffer.cpp
Normal file
@ -0,0 +1,108 @@
|
||||
#include "obs-websocket.h"
|
||||
#include "WSEvents.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
|
||||
/**
|
||||
* Get the status of the OBS replay buffer.
|
||||
*
|
||||
* @return {boolean} `isReplayBufferActive` Current recording status.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetReplayBufferStatus
|
||||
* @category replay buffer
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetReplayBufferStatus(const RpcRequest& request) {
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_bool(data, "isReplayBufferActive", obs_frontend_replay_buffer_active());
|
||||
|
||||
return request.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the Replay Buffer on/off (depending on the current state of the replay buffer).
|
||||
*
|
||||
* @api requests
|
||||
* @name StartStopReplayBuffer
|
||||
* @category replay buffer
|
||||
* @since 4.2.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::StartStopReplayBuffer(const RpcRequest& request) {
|
||||
if (obs_frontend_replay_buffer_active()) {
|
||||
obs_frontend_replay_buffer_stop();
|
||||
} else {
|
||||
Utils::StartReplayBuffer();
|
||||
}
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start recording into the Replay Buffer.
|
||||
* Will return an `error` if the Replay Buffer is already active or if the
|
||||
* "Save Replay Buffer" hotkey is not set in OBS' settings.
|
||||
* Setting this hotkey is mandatory, even when triggering saves only
|
||||
* through obs-websocket.
|
||||
*
|
||||
* @api requests
|
||||
* @name StartReplayBuffer
|
||||
* @category replay buffer
|
||||
* @since 4.2.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::StartReplayBuffer(const RpcRequest& request) {
|
||||
if (!Utils::ReplayBufferEnabled()) {
|
||||
return request.failed("replay buffer disabled in settings");
|
||||
}
|
||||
|
||||
if (obs_frontend_replay_buffer_active() == true) {
|
||||
return request.failed("replay buffer already active");
|
||||
}
|
||||
|
||||
Utils::StartReplayBuffer();
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop recording into the Replay Buffer.
|
||||
* Will return an `error` if the Replay Buffer is not active.
|
||||
*
|
||||
* @api requests
|
||||
* @name StopReplayBuffer
|
||||
* @category replay buffer
|
||||
* @since 4.2.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::StopReplayBuffer(const RpcRequest& request) {
|
||||
if (obs_frontend_replay_buffer_active() == true) {
|
||||
obs_frontend_replay_buffer_stop();
|
||||
return request.success();
|
||||
} else {
|
||||
return request.failed("replay buffer not active");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush and save the contents of the Replay Buffer to disk. This is
|
||||
* basically the same as triggering the "Save Replay Buffer" hotkey.
|
||||
* Will return an `error` if the Replay Buffer is not active.
|
||||
*
|
||||
* @api requests
|
||||
* @name SaveReplayBuffer
|
||||
* @category replay buffer
|
||||
* @since 4.2.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::SaveReplayBuffer(const RpcRequest& request) {
|
||||
if (!obs_frontend_replay_buffer_active()) {
|
||||
return request.failed("replay buffer not active");
|
||||
}
|
||||
|
||||
OBSOutputAutoRelease replayOutput = obs_frontend_get_replay_buffer_output();
|
||||
|
||||
calldata_t cd = { 0 };
|
||||
proc_handler_t* ph = obs_output_get_proc_handler(replayOutput);
|
||||
proc_handler_call(ph, "save", &cd);
|
||||
calldata_free(&cd);
|
||||
|
||||
return request.success();
|
||||
}
|
84
src/WSRequestHandler_SceneCollections.cpp
Normal file
84
src/WSRequestHandler_SceneCollections.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
#include "Utils.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
/**
|
||||
* @typedef {Object} `ScenesCollection`
|
||||
* @property {String} `sc-name` Name of the scene collection
|
||||
*/
|
||||
|
||||
/**
|
||||
* Change the active scene collection.
|
||||
*
|
||||
* @param {String} `sc-name` Name of the desired scene collection.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetCurrentSceneCollection
|
||||
* @category scene collections
|
||||
* @since 4.0.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::SetCurrentSceneCollection(const RpcRequest& request) {
|
||||
if (!request.hasField("sc-name")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
const char* sceneCollection = obs_data_get_string(request.parameters(), "sc-name");
|
||||
if (!sceneCollection) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
char** collections = obs_frontend_get_scene_collections();
|
||||
bool collectionExists = Utils::StringInStringList(collections, sceneCollection);
|
||||
bfree(collections);
|
||||
if (!collectionExists) {
|
||||
return request.failed("scene collection does not exist");
|
||||
}
|
||||
|
||||
obs_queue_task(OBS_TASK_UI, [](void* param) {
|
||||
obs_frontend_set_current_scene_collection(reinterpret_cast<const char*>(param));
|
||||
}, (void*)sceneCollection, true);
|
||||
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the current scene collection.
|
||||
*
|
||||
* @return {String} `sc-name` Name of the currently active scene collection.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetCurrentSceneCollection
|
||||
* @category scene collections
|
||||
* @since 4.0.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetCurrentSceneCollection(const RpcRequest& request) {
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
|
||||
char* sceneCollection = obs_frontend_get_current_scene_collection();
|
||||
obs_data_set_string(response, "sc-name", sceneCollection);
|
||||
bfree(sceneCollection);
|
||||
|
||||
return request.success(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* List available scene collections
|
||||
*
|
||||
* @return {Array<ScenesCollection>} `scene-collections` Scene collections list
|
||||
*
|
||||
* @api requests
|
||||
* @name ListSceneCollections
|
||||
* @category scene collections
|
||||
* @since 4.0.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::ListSceneCollections(const RpcRequest& request) {
|
||||
char** sceneCollections = obs_frontend_get_scene_collections();
|
||||
OBSDataArrayAutoRelease list =
|
||||
Utils::StringListToArray(sceneCollections, "sc-name");
|
||||
bfree(sceneCollections);
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_array(response, "scene-collections", list);
|
||||
|
||||
return request.success(response);
|
||||
}
|
753
src/WSRequestHandler_SceneItems.cpp
Normal file
753
src/WSRequestHandler_SceneItems.cpp
Normal file
@ -0,0 +1,753 @@
|
||||
#include "Utils.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
/**
|
||||
* Get a list of all scene items in a scene.
|
||||
*
|
||||
* @param {String (optional)} `sceneName` Name of the scene to get the list of scene items from. Defaults to the current scene if not specified.
|
||||
*
|
||||
* @return {String} `sceneName` Name of the requested (or current) scene
|
||||
* @return {Array<Object>} `sceneItems` Array of scene items
|
||||
* @return {int} `sceneItems.*.itemId` Unique item id of the source item
|
||||
* @return {String} `sceneItems.*.sourceKind` ID if the scene item's source. For example `vlc_source` or `image_source`
|
||||
* @return {String} `sceneItems.*.sourceName` Name of the scene item's source
|
||||
* @return {String} `sceneItems.*.sourceType` Type of the scene item's source. Either `input`, `group`, or `scene`
|
||||
*
|
||||
* @api requests
|
||||
* @name GetSceneItemList
|
||||
* @category scene items
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetSceneItemList(const RpcRequest& request) {
|
||||
const char* sceneName = obs_data_get_string(request.parameters(), "sceneName");
|
||||
|
||||
OBSSourceAutoRelease sceneSource;
|
||||
if (sceneName && strcmp(sceneName, "") != 0) {
|
||||
sceneSource = obs_get_source_by_name(sceneName);
|
||||
} else {
|
||||
sceneSource = obs_frontend_get_current_scene();
|
||||
}
|
||||
|
||||
OBSScene scene = obs_scene_from_source(sceneSource);
|
||||
if (!scene) {
|
||||
return request.failed("requested scene is invalid or doesnt exist");
|
||||
}
|
||||
|
||||
OBSDataArrayAutoRelease sceneItemArray = obs_data_array_create();
|
||||
|
||||
auto sceneItemEnumProc = [](obs_scene_t *, obs_sceneitem_t* item, void* privateData) -> bool {
|
||||
obs_data_array_t* sceneItemArray = (obs_data_array_t*)privateData;
|
||||
|
||||
OBSDataAutoRelease sceneItemData = obs_data_create();
|
||||
obs_data_set_int(sceneItemData, "itemId", obs_sceneitem_get_id(item));
|
||||
OBSSource source = obs_sceneitem_get_source(item);
|
||||
obs_data_set_string(sceneItemData, "sourceKind", obs_source_get_id(source));
|
||||
obs_data_set_string(sceneItemData, "sourceName", obs_source_get_name(source));
|
||||
|
||||
QString typeString = "";
|
||||
enum obs_source_type sourceType = obs_source_get_type(source);
|
||||
switch (sourceType) {
|
||||
case OBS_SOURCE_TYPE_INPUT:
|
||||
typeString = "input";
|
||||
break;
|
||||
|
||||
case OBS_SOURCE_TYPE_SCENE:
|
||||
typeString = "scene";
|
||||
break;
|
||||
|
||||
default:
|
||||
typeString = "unknown";
|
||||
break;
|
||||
}
|
||||
obs_data_set_string(sceneItemData, "sourceType", typeString.toUtf8());
|
||||
|
||||
obs_data_array_push_back(sceneItemArray, sceneItemData);
|
||||
return true;
|
||||
};
|
||||
obs_scene_enum_items(scene, sceneItemEnumProc, sceneItemArray);
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "sceneName", obs_source_get_name(sceneSource));
|
||||
obs_data_set_array(response, "sceneItems", sceneItemArray);
|
||||
|
||||
return request.success(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scene specific properties of the specified source item.
|
||||
* Coordinates are relative to the item's parent (the scene or group it belongs to).
|
||||
*
|
||||
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
|
||||
* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).
|
||||
* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object)
|
||||
* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object)
|
||||
*
|
||||
* @return {String} `name` Scene Item name.
|
||||
* @return {int} `itemId` Scene Item ID.
|
||||
* @return {double} `position.x` The x position of the source from the left.
|
||||
* @return {double} `position.y` The y position of the source from the top.
|
||||
* @return {int} `position.alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.
|
||||
* @return {double} `rotation` The clockwise rotation of the item in degrees around the point of alignment.
|
||||
* @return {double} `scale.x` The x-scale factor of the source.
|
||||
* @return {double} `scale.y` The y-scale factor of the source.
|
||||
* @return {int} `crop.top` The number of pixels cropped off the top of the source before scaling.
|
||||
* @return {int} `crop.right` The number of pixels cropped off the right of the source before scaling.
|
||||
* @return {int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling.
|
||||
* @return {int} `crop.left` The number of pixels cropped off the left of the source before scaling.
|
||||
* @return {bool} `visible` If the source is visible.
|
||||
* @return {bool} `muted` If the source is muted.
|
||||
* @return {bool} `locked` If the source's transform is locked.
|
||||
* @return {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
|
||||
* @return {int} `bounds.alignment` Alignment of the bounding box.
|
||||
* @return {double} `bounds.x` Width of the bounding box.
|
||||
* @return {double} `bounds.y` Height of the bounding box.
|
||||
* @return {int} `sourceWidth` Base width (without scaling) of the source
|
||||
* @return {int} `sourceHeight` Base source (without scaling) of the source
|
||||
* @return {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)
|
||||
* @return {double} `height` Scene item height (base source height multiplied by the vertical scaling factor)
|
||||
* @return {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
|
||||
* @return {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)
|
||||
*
|
||||
* @api requests
|
||||
* @name GetSceneItemProperties
|
||||
* @category scene items
|
||||
* @since 4.3.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::GetSceneItemProperties(const RpcRequest& request) {
|
||||
if (!request.hasField("item")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
OBSData params = request.parameters();
|
||||
|
||||
QString sceneName = obs_data_get_string(params, "scene-name");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
return request.failed("requested scene doesn't exist");
|
||||
}
|
||||
|
||||
OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item");
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
|
||||
if (!sceneItem) {
|
||||
return request.failed("specified scene item doesn't exist");
|
||||
}
|
||||
|
||||
OBSDataAutoRelease data = Utils::GetSceneItemPropertiesData(sceneItem);
|
||||
|
||||
OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem);
|
||||
obs_data_set_string(data, "name", obs_source_get_name(sceneItemSource));
|
||||
obs_data_set_int(data, "itemId", obs_sceneitem_get_id(sceneItem));
|
||||
|
||||
return request.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scene specific properties of a source. Unspecified properties will remain unchanged.
|
||||
* Coordinates are relative to the item's parent (the scene or group it belongs to).
|
||||
*
|
||||
* @param {String (optional)} `scene-name` Name of the scene the source item belongs to. Defaults to the current scene.
|
||||
* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).
|
||||
* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object)
|
||||
* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object)
|
||||
* @param {double (optional)} `position.x` The new x position of the source.
|
||||
* @param {double (optional)} `position.y` The new y position of the source.
|
||||
* @param {int (optional)} `position.alignment` The new alignment of the source.
|
||||
* @param {double (optional)} `rotation` The new clockwise rotation of the item in degrees.
|
||||
* @param {double (optional)} `scale.x` The new x scale of the item.
|
||||
* @param {double (optional)} `scale.y` The new y scale of the item.
|
||||
* @param {int (optional)} `crop.top` The new amount of pixels cropped off the top of the source before scaling.
|
||||
* @param {int (optional)} `crop.bottom` The new amount of pixels cropped off the bottom of the source before scaling.
|
||||
* @param {int (optional)} `crop.left` The new amount of pixels cropped off the left of the source before scaling.
|
||||
* @param {int (optional)} `crop.right` The new amount of pixels cropped off the right of the source before scaling.
|
||||
* @param {bool (optional)} `visible` The new visibility of the source. 'true' shows source, 'false' hides source.
|
||||
* @param {bool (optional)} `locked` The new locked status of the source. 'true' keeps it in its current position, 'false' allows movement.
|
||||
* @param {String (optional)} `bounds.type` The new bounds type of the source. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
|
||||
* @param {int (optional)} `bounds.alignment` The new alignment of the bounding box. (0-2, 4-6, 8-10)
|
||||
* @param {double (optional)} `bounds.x` The new width of the bounding box.
|
||||
* @param {double (optional)} `bounds.y` The new height of the bounding box.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetSceneItemProperties
|
||||
* @category scene items
|
||||
* @since 4.3.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request) {
|
||||
if (!request.hasField("item")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
OBSData params = request.parameters();
|
||||
|
||||
QString sceneName = obs_data_get_string(params, "scene-name");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
return request.failed("requested scene doesn't exist");
|
||||
}
|
||||
|
||||
OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item");
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
|
||||
if (!sceneItem) {
|
||||
return request.failed("specified scene item doesn't exist");
|
||||
}
|
||||
|
||||
bool badRequest = false;
|
||||
OBSDataAutoRelease errorData = obs_data_create();
|
||||
|
||||
obs_sceneitem_defer_update_begin(sceneItem);
|
||||
|
||||
if (request.hasField("position")) {
|
||||
vec2 oldPosition;
|
||||
OBSDataAutoRelease positionError = obs_data_create();
|
||||
obs_sceneitem_get_pos(sceneItem, &oldPosition);
|
||||
|
||||
OBSDataAutoRelease reqPosition = obs_data_get_obj(params, "position");
|
||||
vec2 newPosition = oldPosition;
|
||||
|
||||
if (obs_data_has_user_value(reqPosition, "x")) {
|
||||
newPosition.x = obs_data_get_double(reqPosition, "x");
|
||||
}
|
||||
if (obs_data_has_user_value(reqPosition, "y")) {
|
||||
newPosition.y = obs_data_get_double(reqPosition, "y");
|
||||
}
|
||||
|
||||
if (obs_data_has_user_value(reqPosition, "alignment")) {
|
||||
const uint32_t alignment = obs_data_get_int(reqPosition, "alignment");
|
||||
if (Utils::IsValidAlignment(alignment)) {
|
||||
obs_sceneitem_set_alignment(sceneItem, alignment);
|
||||
} else {
|
||||
badRequest = true;
|
||||
obs_data_set_string(positionError, "alignment", "invalid");
|
||||
obs_data_set_obj(errorData, "position", positionError);
|
||||
}
|
||||
}
|
||||
|
||||
obs_sceneitem_set_pos(sceneItem, &newPosition);
|
||||
}
|
||||
|
||||
if (request.hasField("rotation")) {
|
||||
obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(params, "rotation"));
|
||||
}
|
||||
|
||||
if (request.hasField("scale")) {
|
||||
vec2 oldScale;
|
||||
obs_sceneitem_get_scale(sceneItem, &oldScale);
|
||||
vec2 newScale = oldScale;
|
||||
|
||||
OBSDataAutoRelease reqScale = obs_data_get_obj(params, "scale");
|
||||
|
||||
if (obs_data_has_user_value(reqScale, "x")) {
|
||||
newScale.x = obs_data_get_double(reqScale, "x");
|
||||
}
|
||||
if (obs_data_has_user_value(reqScale, "y")) {
|
||||
newScale.y = obs_data_get_double(reqScale, "y");
|
||||
}
|
||||
|
||||
obs_sceneitem_set_scale(sceneItem, &newScale);
|
||||
}
|
||||
|
||||
if (request.hasField("crop")) {
|
||||
obs_sceneitem_crop oldCrop;
|
||||
obs_sceneitem_get_crop(sceneItem, &oldCrop);
|
||||
|
||||
OBSDataAutoRelease reqCrop = obs_data_get_obj(params, "crop");
|
||||
obs_sceneitem_crop newCrop = oldCrop;
|
||||
|
||||
if (obs_data_has_user_value(reqCrop, "top")) {
|
||||
newCrop.top = obs_data_get_int(reqCrop, "top");
|
||||
}
|
||||
if (obs_data_has_user_value(reqCrop, "right")) {
|
||||
newCrop.right = obs_data_get_int(reqCrop, "right");
|
||||
}
|
||||
if (obs_data_has_user_value(reqCrop, "bottom")) {
|
||||
newCrop.bottom = obs_data_get_int(reqCrop, "bottom");
|
||||
}
|
||||
if (obs_data_has_user_value(reqCrop, "left")) {
|
||||
newCrop.left = obs_data_get_int(reqCrop, "left");
|
||||
}
|
||||
|
||||
obs_sceneitem_set_crop(sceneItem, &newCrop);
|
||||
}
|
||||
|
||||
if (request.hasField("visible")) {
|
||||
obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(params, "visible"));
|
||||
}
|
||||
|
||||
if (request.hasField("locked")) {
|
||||
obs_sceneitem_set_locked(sceneItem, obs_data_get_bool(params, "locked"));
|
||||
}
|
||||
|
||||
if (request.hasField("bounds")) {
|
||||
bool badBounds = false;
|
||||
OBSDataAutoRelease boundsError = obs_data_create();
|
||||
OBSDataAutoRelease reqBounds = obs_data_get_obj(params, "bounds");
|
||||
|
||||
if (obs_data_has_user_value(reqBounds, "type")) {
|
||||
QString newBoundsType = obs_data_get_string(reqBounds, "type");
|
||||
if (newBoundsType == "OBS_BOUNDS_NONE") {
|
||||
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_NONE);
|
||||
}
|
||||
else if (newBoundsType == "OBS_BOUNDS_STRETCH") {
|
||||
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_STRETCH);
|
||||
}
|
||||
else if (newBoundsType == "OBS_BOUNDS_SCALE_INNER") {
|
||||
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_INNER);
|
||||
}
|
||||
else if (newBoundsType == "OBS_BOUNDS_SCALE_OUTER") {
|
||||
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_OUTER);
|
||||
}
|
||||
else if (newBoundsType == "OBS_BOUNDS_SCALE_TO_WIDTH") {
|
||||
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_TO_WIDTH);
|
||||
}
|
||||
else if (newBoundsType == "OBS_BOUNDS_SCALE_TO_HEIGHT") {
|
||||
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_TO_HEIGHT);
|
||||
}
|
||||
else if (newBoundsType == "OBS_BOUNDS_MAX_ONLY") {
|
||||
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_MAX_ONLY);
|
||||
}
|
||||
else {
|
||||
badRequest = badBounds = true;
|
||||
obs_data_set_string(boundsError, "type", "invalid");
|
||||
}
|
||||
}
|
||||
|
||||
vec2 oldBounds;
|
||||
obs_sceneitem_get_bounds(sceneItem, &oldBounds);
|
||||
vec2 newBounds = oldBounds;
|
||||
|
||||
if (obs_data_has_user_value(reqBounds, "x")) {
|
||||
newBounds.x = obs_data_get_double(reqBounds, "x");
|
||||
}
|
||||
if (obs_data_has_user_value(reqBounds, "y")) {
|
||||
newBounds.y = obs_data_get_double(reqBounds, "y");
|
||||
}
|
||||
|
||||
obs_sceneitem_set_bounds(sceneItem, &newBounds);
|
||||
|
||||
if (obs_data_has_user_value(reqBounds, "alignment")) {
|
||||
const uint32_t bounds_alignment = obs_data_get_int(reqBounds, "alignment");
|
||||
if (Utils::IsValidAlignment(bounds_alignment)) {
|
||||
obs_sceneitem_set_bounds_alignment(sceneItem, bounds_alignment);
|
||||
}
|
||||
else {
|
||||
badRequest = badBounds = true;
|
||||
obs_data_set_string(boundsError, "alignment", "invalid");
|
||||
}
|
||||
}
|
||||
|
||||
if (badBounds) {
|
||||
obs_data_set_obj(errorData, "bounds", boundsError);
|
||||
}
|
||||
}
|
||||
|
||||
obs_sceneitem_defer_update_end(sceneItem);
|
||||
|
||||
if (badRequest) {
|
||||
return request.failed("error", errorData);
|
||||
}
|
||||
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset a scene item.
|
||||
*
|
||||
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
|
||||
* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).
|
||||
* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object)
|
||||
* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object)
|
||||
*
|
||||
* @api requests
|
||||
* @name ResetSceneItem
|
||||
* @category scene items
|
||||
* @since 4.2.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::ResetSceneItem(const RpcRequest& request) {
|
||||
if (!request.hasField("item")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
OBSData params = request.parameters();
|
||||
|
||||
const char* sceneName = obs_data_get_string(params, "scene-name");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
return request.failed("requested scene doesn't exist");
|
||||
}
|
||||
|
||||
OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item");
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
|
||||
if (!sceneItem) {
|
||||
return request.failed("specified scene item doesn't exist");
|
||||
}
|
||||
|
||||
OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem);
|
||||
|
||||
OBSDataAutoRelease settings = obs_source_get_settings(sceneItemSource);
|
||||
obs_source_update(sceneItemSource, settings);
|
||||
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide a specified source item in a specified scene.
|
||||
*
|
||||
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the currently active scene.
|
||||
* @param {String (optional)} `source` Scene Item name.
|
||||
* @param {int (optional)} `item` Scene Item id
|
||||
* @param {boolean} `render` true = shown ; false = hidden
|
||||
*
|
||||
* @api requests
|
||||
* @name SetSceneItemRender
|
||||
* @category scene items
|
||||
* @since 0.3
|
||||
*/
|
||||
RpcResponse WSRequestHandler::SetSceneItemRender(const RpcRequest& request) {
|
||||
bool doesntHaveSourceOrItemParameter = !(request.hasField("source") || request.hasField("item"));
|
||||
if (!request.hasField("render") || doesntHaveSourceOrItemParameter)
|
||||
{
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
const char* itemName = obs_data_get_string(request.parameters(), "source");
|
||||
int64_t itemId = obs_data_get_int(request.parameters(), "item");
|
||||
bool isVisible = obs_data_get_bool(request.parameters(), "render");
|
||||
|
||||
if (!itemName && !itemId) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
const char* sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
return request.failed("requested scene doesn't exist");
|
||||
}
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem;
|
||||
|
||||
if (strlen(itemName)) {
|
||||
sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||
if (!sceneItem) {
|
||||
return request.failed("specified scene item name doesn't exist");
|
||||
}
|
||||
} else {
|
||||
sceneItem = Utils::GetSceneItemFromId(scene, itemId);
|
||||
if (!sceneItem) {
|
||||
return request.failed("specified scene item ID doesn't exist");
|
||||
}
|
||||
}
|
||||
obs_sceneitem_set_visible(sceneItem, isVisible);
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the coordinates of a specified source item.
|
||||
*
|
||||
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
|
||||
* @param {String} `item` Scene Item name.
|
||||
* @param {double} `x` X coordinate.
|
||||
* @param {double} `y` Y coordinate.
|
||||
|
||||
*
|
||||
* @api requests
|
||||
* @name SetSceneItemPosition
|
||||
* @category scene items
|
||||
* @since 4.0.0
|
||||
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||
*/
|
||||
RpcResponse WSRequestHandler::SetSceneItemPosition(const RpcRequest& request) {
|
||||
if (!request.hasField("item") ||
|
||||
!request.hasField("x") || !request.hasField("y")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
QString itemName = obs_data_get_string(request.parameters(), "item");
|
||||
if (itemName.isEmpty()) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
return request.failed("requested scene could not be found");
|
||||
}
|
||||
|
||||
OBSSceneItem sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||
if (!sceneItem) {
|
||||
return request.failed("specified scene item doesn't exist");
|
||||
}
|
||||
|
||||
vec2 item_position = { 0 };
|
||||
item_position.x = obs_data_get_double(request.parameters(), "x");
|
||||
item_position.y = obs_data_get_double(request.parameters(), "y");
|
||||
obs_sceneitem_set_pos(sceneItem, &item_position);
|
||||
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the transform of the specified source item.
|
||||
*
|
||||
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
|
||||
* @param {String} `item` Scene Item name.
|
||||
* @param {double} `x-scale` Width scale factor.
|
||||
* @param {double} `y-scale` Height scale factor.
|
||||
* @param {double} `rotation` Source item rotation (in degrees).
|
||||
*
|
||||
* @api requests
|
||||
* @name SetSceneItemTransform
|
||||
* @category scene items
|
||||
* @since 4.0.0
|
||||
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||
*/
|
||||
RpcResponse WSRequestHandler::SetSceneItemTransform(const RpcRequest& request) {
|
||||
if (!request.hasField("item") ||
|
||||
!request.hasField("x-scale") ||
|
||||
!request.hasField("y-scale") ||
|
||||
!request.hasField("rotation"))
|
||||
{
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
QString itemName = obs_data_get_string(request.parameters(), "item");
|
||||
if (itemName.isEmpty()) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
return request.failed("requested scene doesn't exist");
|
||||
}
|
||||
|
||||
vec2 scale;
|
||||
scale.x = obs_data_get_double(request.parameters(), "x-scale");
|
||||
scale.y = obs_data_get_double(request.parameters(), "y-scale");
|
||||
float rotation = obs_data_get_double(request.parameters(), "rotation");
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||
if (!sceneItem) {
|
||||
return request.failed("specified scene item doesn't exist");
|
||||
}
|
||||
|
||||
obs_sceneitem_defer_update_begin(sceneItem);
|
||||
|
||||
obs_sceneitem_set_scale(sceneItem, &scale);
|
||||
obs_sceneitem_set_rot(sceneItem, rotation);
|
||||
|
||||
obs_sceneitem_defer_update_end(sceneItem);
|
||||
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the crop coordinates of the specified source item.
|
||||
*
|
||||
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
|
||||
* @param {String} `item` Scene Item name.
|
||||
* @param {int} `top` Pixel position of the top of the source item.
|
||||
* @param {int} `bottom` Pixel position of the bottom of the source item.
|
||||
* @param {int} `left` Pixel position of the left of the source item.
|
||||
* @param {int} `right` Pixel position of the right of the source item.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetSceneItemCrop
|
||||
* @category scene items
|
||||
* @since 4.1.0
|
||||
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||
*/
|
||||
RpcResponse WSRequestHandler::SetSceneItemCrop(const RpcRequest& request) {
|
||||
if (!request.hasField("item")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
QString itemName = obs_data_get_string(request.parameters(), "item");
|
||||
if (itemName.isEmpty()) {
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
return request.failed("requested scene doesn't exist");
|
||||
}
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||
if (!sceneItem) {
|
||||
return request.failed("specified scene item doesn't exist");
|
||||
}
|
||||
|
||||
struct obs_sceneitem_crop crop = { 0 };
|
||||
crop.top = obs_data_get_int(request.parameters(), "top");
|
||||
crop.bottom = obs_data_get_int(request.parameters(), "bottom");
|
||||
crop.left = obs_data_get_int(request.parameters(), "left");
|
||||
crop.right = obs_data_get_int(request.parameters(), "right");
|
||||
|
||||
obs_sceneitem_set_crop(sceneItem, &crop);
|
||||
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a scene item.
|
||||
*
|
||||
* @param {String (optional)} `scene` Name of the scene the scene item belongs to. Defaults to the current scene.
|
||||
* @param {Object} `item` Scene item to delete (required)
|
||||
* @param {String} `item.name` Scene Item name (prefer `id`, including both is acceptable).
|
||||
* @param {int} `item.id` Scene Item ID.
|
||||
*
|
||||
* @api requests
|
||||
* @name DeleteSceneItem
|
||||
* @category scene items
|
||||
* @since 4.5.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::DeleteSceneItem(const RpcRequest& request) {
|
||||
if (!request.hasField("item")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
const char* sceneName = obs_data_get_string(request.parameters(), "scene");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
return request.failed("requested scene doesn't exist");
|
||||
}
|
||||
|
||||
OBSDataItemAutoRelease itemField = obs_data_item_byname(request.parameters(), "item");
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
|
||||
if (!sceneItem) {
|
||||
return request.failed("item with id/name combination not found in specified scene");
|
||||
}
|
||||
|
||||
obs_sceneitem_remove(sceneItem);
|
||||
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a scene item in a scene. In other words, this is how you add a source into a scene.
|
||||
*
|
||||
* @param {String} `sceneName` Name of the scene to create the scene item in
|
||||
* @param {String} `sourceName` Name of the source to be added
|
||||
* @param {boolean} `setVisible` Whether to make the sceneitem visible on creation or not. Default `true`
|
||||
*
|
||||
* @return {int} `itemId` Numerical ID of the created scene item
|
||||
*
|
||||
* @api requests
|
||||
* @name AddSceneItem
|
||||
* @category scene items
|
||||
* @since 4.9.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::AddSceneItem(const RpcRequest& request) {
|
||||
if (!request.hasField("sceneName") || !request.hasField("sourceName")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
const char* sceneName = obs_data_get_string(request.parameters(), "sceneName");
|
||||
OBSSourceAutoRelease sceneSource = obs_get_source_by_name(sceneName);
|
||||
OBSScene scene = obs_scene_from_source(sceneSource);
|
||||
if (!scene) {
|
||||
return request.failed("requested scene is invalid or doesnt exist");
|
||||
}
|
||||
|
||||
const char* sourceName = obs_data_get_string(request.parameters(), "sourceName");
|
||||
OBSSourceAutoRelease source = obs_get_source_by_name(sourceName);
|
||||
if (!source) {
|
||||
return request.failed("requested source does not exist");
|
||||
}
|
||||
|
||||
if (source == sceneSource) {
|
||||
return request.failed("you cannot add a scene as a sceneitem to itself");
|
||||
}
|
||||
|
||||
Utils::AddSourceData data;
|
||||
data.source = source;
|
||||
data.setVisible = true;
|
||||
if (request.hasField("setVisible")) {
|
||||
data.setVisible = obs_data_get_bool(request.parameters(), "setVisible");
|
||||
}
|
||||
|
||||
obs_enter_graphics();
|
||||
obs_scene_atomic_update(scene, Utils::AddSourceHelper, &data);
|
||||
obs_leave_graphics();
|
||||
|
||||
OBSDataAutoRelease responseData = obs_data_create();
|
||||
obs_data_set_int(responseData, "itemId", obs_sceneitem_get_id(data.sceneItem));
|
||||
|
||||
return request.success(responseData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicates a scene item.
|
||||
*
|
||||
* @param {String (optional)} `fromScene` Name of the scene to copy the item from. Defaults to the current scene.
|
||||
* @param {String (optional)} `toScene` Name of the scene to create the item in. Defaults to the current scene.
|
||||
* @param {Object} `item` Scene Item to duplicate from the source scene (required)
|
||||
* @param {String} `item.name` Scene Item name (prefer `id`, including both is acceptable).
|
||||
* @param {int} `item.id` Scene Item ID.
|
||||
*
|
||||
* @return {String} `scene` Name of the scene where the new item was created
|
||||
* @return {Object} `item` New item info
|
||||
* @return {int} `item.id` New item ID
|
||||
* @return {String} `item.name` New item name
|
||||
*
|
||||
* @api requests
|
||||
* @name DuplicateSceneItem
|
||||
* @category scene items
|
||||
* @since 4.5.0
|
||||
*/
|
||||
RpcResponse WSRequestHandler::DuplicateSceneItem(const RpcRequest& request) {
|
||||
struct DuplicateSceneItemData {
|
||||
obs_sceneitem_t *referenceItem;
|
||||
obs_source_t *fromSource;
|
||||
obs_sceneitem_t *newItem;
|
||||
};
|
||||
|
||||
if (!request.hasField("item")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
const char* fromSceneName = obs_data_get_string(request.parameters(), "fromScene");
|
||||
OBSScene fromScene = Utils::GetSceneFromNameOrCurrent(fromSceneName);
|
||||
if (!fromScene) {
|
||||
return request.failed("requested fromScene doesn't exist");
|
||||
}
|
||||
|
||||
const char* toSceneName = obs_data_get_string(request.parameters(), "toScene");
|
||||
OBSScene toScene = Utils::GetSceneFromNameOrCurrent(toSceneName);
|
||||
if (!toScene) {
|
||||
return request.failed("requested toScene doesn't exist");
|
||||
}
|
||||
|
||||
OBSDataItemAutoRelease itemField = obs_data_item_byname(request.parameters(), "item");
|
||||
OBSSceneItemAutoRelease referenceItem = Utils::GetSceneItemFromRequestField(fromScene, itemField);
|
||||
if (!referenceItem) {
|
||||
return request.failed("item with id/name combination not found in specified scene");
|
||||
}
|
||||
|
||||
DuplicateSceneItemData data;
|
||||
data.fromSource = obs_sceneitem_get_source(referenceItem);
|
||||
data.referenceItem = referenceItem;
|
||||
|
||||
obs_enter_graphics();
|
||||
obs_scene_atomic_update(toScene, [](void *_data, obs_scene_t *scene) {
|
||||
auto data = reinterpret_cast<DuplicateSceneItemData*>(_data);
|
||||
data->newItem = obs_scene_add(scene, data->fromSource);
|
||||
obs_sceneitem_set_visible(data->newItem, obs_sceneitem_visible(data->referenceItem));
|
||||
}, &data);
|
||||
obs_leave_graphics();
|
||||
|
||||
obs_sceneitem_t *newItem = data.newItem;
|
||||
if (!newItem) {
|
||||
return request.failed("Error duplicating scene item");
|
||||
}
|
||||
|
||||
OBSDataAutoRelease itemData = obs_data_create();
|
||||
obs_data_set_int(itemData, "id", obs_sceneitem_get_id(newItem));
|
||||
obs_data_set_string(itemData, "name", obs_source_get_name(obs_sceneitem_get_source(newItem)));
|
||||
|
||||
OBSDataAutoRelease responseData = obs_data_create();
|
||||
obs_data_set_obj(responseData, "item", itemData);
|
||||
obs_data_set_string(responseData, "scene", obs_source_get_name(obs_scene_get_source(toScene)));
|
||||
|
||||
return request.success(responseData);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user