pax_global_header00006660000000000000000000000064140110746760014520gustar00rootroot0000000000000052 comment=2f252f67ba627fca45d093d02db7d899e13dca46 obs-websocket-4.9.0/000077500000000000000000000000001401107467600143015ustar00rootroot00000000000000obs-websocket-4.9.0/.editorconfig000066400000000000000000000002131401107467600167520ustar00rootroot00000000000000[*] insert_final_newline = true [*.{c,cpp,h,hpp}] indent_style = tab indent_size = 4 [*.{yml,yaml}] indent_style = space indent_size = 2 obs-websocket-4.9.0/.github/000077500000000000000000000000001401107467600156415ustar00rootroot00000000000000obs-websocket-4.9.0/.github/FUNDING.yml000066400000000000000000000001331401107467600174530ustar00rootroot00000000000000open_collective: obs-websocket github: Palakis custom: https://www.paypal.me/stephanelepin obs-websocket-4.9.0/.github/ISSUE_TEMPLATE.md000066400000000000000000000013361401107467600203510ustar00rootroot00000000000000##### Issue type ##### Description ##### Steps to reproduce and other useful info ##### Technical information - **Operating System** : - **OBS Studio version** : - **obs-websocket version** : ##### Development Environment obs-websocket-4.9.0/.github/images/000077500000000000000000000000001401107467600171065ustar00rootroot00000000000000obs-websocket-4.9.0/.github/images/mediaunit_logo_black.png000066400000000000000000001534611401107467600237610ustar00rootroot00000000000000PNG  IHDR6ƄsBIT|d pHYs  ~tEXtSoftwareAdobe Fireworks CS6輲tEXtCreation Time15-05-15;prVWxM0_4--h E *^耐xX'9,to۾iu]׶m4ÁO;-8.|0n"^(]v)9\~vW{'n7">(hΆpc1wIJCm%Ɋ~MH 6O* 묿 qT_iWp"WsF̯ˎr7i j}xY׫jƊxg~*ֿ,E5aUuU`7X+o=rWW~n_|2i_ٯuTU0a0v!n 3kxH#/%/[O2+2__C>+OjK&O_OJGz܏5,?t{3|[8'^????~wtO@{3b8s'/"HmkBF)3amkTSx}Ʊ'77#Y~%d?g??SR2#1Ñ-AldW}f PH'H< UuZHt!ރ1^>S644=9Oӷ;m:G=ާogyhwN'p|n[S{SKn(⨒#9|đ#AcaFfՃ7_lb [r}*g |)FF{X!aVj<AGw?//Wty;8{RiCǣѲşZq!n9$-\M'[NaÎGv#19v/pKdl fIbrK`l²N-W(Mol[j1H* {U8%GMj-r >7E>RFVe@oT3fVϮ6=ѷ ^y*恎M /mwL'Oלg+xea?ޱeoX)/ZU#ȯKک[>2PRғ[Zn[5eC^x1>~Z蟑\4\LޖrѪXjf7Q% `\8N:K楲vO:)9X:CVCa$c@_C6\2il쉫!SrUHoVn@mYWA.Ra/٬o*5ғOt @$0ɏ2Is!!I,k˚IJ&ɚ/*VcO'׿)!e9eߧM${+ KMʲIY6,fM ?t[%Si9z L-,6$$ɅڲJ[o #yw LO])y65R3 f6oZAc$][SgUi'ͯCUNU[ޙ5q}]IBJ$O[0ծ\B5i,cYpzKEbjmsf316 /,SF X K1kPG )_GʉQ~!VcJ8uP1 z+Yf3T:^}mjD6pG2LP]V-c2v;~-eirrҳ9]/ׅDQ鮅bы^u׎ZtOZ@"VЖ8m4qd0)U]5TUS.jl>GyYNSܒqj$@א];F?m{)W+f^c[WoNO()taPg3wd4+.p&i]]pX]Kjo]}ޔ mE9C5܃_u W˳MWȳ2@/Myf(bΆ!NDxdjXZ{Uw><uyӹe2Ru=f-6cහhnit[W_jcy2ղd՚yF٣FNl4?-qS[BkF\U)Eh͈,qHR^8Xc1>*CYy3 _9b/1=ؔ:Cal ӠA]ՔZvܩ!mm|3T8H9]!$%鏮߮*E*Wa?X|_Ii>= <&\Rd50ԌOEsNXaR[)\#߯Ӕrܫw3Φ8#@Y=նg@D9_6 àzcXI:s:ZM wѥ{ N3bIKWaC݁T2gASx ?qL-۲>IiD(E+q߷1zn|<mX0{d<`2 ٵN`R4k>mXU*WƲ%tc詏 ·W='o3̂hT>X?D4gb:k`k*x? V M"u.f8EEETC* xص⣌[`#Qbҩɇv嗉8E @aL qRVcn`(U_zb{hJmdF$\iKxm*fKeRxu|r2E q<5HP;8q[[plp|DZ||vHc nV,Ƚ36oH#;l7$n{Ո̫׍%W/ }??<{|˱}JBXyȽ}ǒö$QĺUKt9`;sL\ר'N|[ '3`&U=!~Q*tl'ؙl'-{K\ @LvAyc PZʝ2 ^6zYͳ9{V.dl fbS?XUrIMYBh W)?ؿ2+l'11fQNq턦Nh[s'#.5NL3GNƊDH?u"ݧW mQee5ĵnUI!Z}#~>_Nt +NtoZī?ؿЖVh˝"WkgURp‘c7N`؝J|tHyJK{~=Ǒ 1nYLJMַs+{Tik/m݊Ob7$?ihPFĴ.JRGsk̪KK B'.oYn;m8eu%j/a$yO{GpC8]*GgV4"rxaVtq=-F=CgRG Yg~vOB TypMqYu>Ygg xg҇&ɔړ+#tΗ,F"/~R5ޣ'ઘDL]a-e!UƧ^Sb_Fd;b1򖔕tY#ב}O__¾#hpCQmkո-F\{O{-[~ֺ`tߣQFGdGs؃p߂^a_GStf28Vުgw6JZgn-=zԝ;Qg\/m&)# 8޲@Ö:9GrsWuz5+Џ;ԯN|g>|g@>\9Wxwy?bwyt:Q6<4yww;!yy;ywGCz7y;rxݝw;λλwyKW=ڼs91]w?3#  ;oZ_RvX_u\.ڛh4 Fa.^Qj}鴭ŘZgﺴoO־6Zp>>6ow#h\c1tQ?p?aXy`! {Ay}7};ѿz->%)炎55(<`MpF`-KУN~2y;ڶѢg l/#JYfG E%EI^({ீ(RfxJK},ѕEhI8俀O9I`QM Ia\<_<Շӽ-ٗǮG>O < 2Z:丹ű}tEw#w@S!w#f?5\>ŵp@P -%~.+O}ҬEs}YasouZqʰ19\Q#i)'ծsWT ܪIZu3cb;b'0ޫս L哓fUJٽaF=گ_)J/yTov/+T?Pn^[M;C;C7 wo{Ϯwktφ*X~Uٯ>j#]lX+UEWI59gga*:FYֵs&O!c0G\W$&X\A=. pyUЪdn![:rUѻ|~ukD|7A*2fa+i %mOjIʢIzx_&}Nk`XVxJNНo;W{p/T5ɻ?G{Nyv^Q/+c鍐z%W*/euH|޽,&4WW8kY2k{vJ)(G֫%y0 ~*E}Uثy3f gZ4a޽&x&߁Tڒs Y+Z`@PIpUZ& EDA^aՄ;GޓtkWpP$oxhiUyY|z2Ü쿠\SFHחc%uu?mw 1xN;\}a=z0^;M.tgː=dkh^_w ݪ[z{7K@WdU MCWy^/9ٗ}=w#uߎ3:OyV?i9x/n?_p$τsI҂eʾfK݈}!^>qŵ՚*lwu_{ҵk2sv+h' Bi_&^|+C,ks牯{'+wx?>_ 6?̟Q.)>$"μ~HnB`FPRє:5try5| 5[p=tAq-hUPJՀM>;l|vO YcQNIVu}7APƌ,X zR:oզSx ]>"1D&M+56]l6c)2[[?'MdBs{7=}'Ǹ9)`zLq2ⷓ)}~D[3<$/R1~FJ<9#OhT..m avx H 8Қ2n+h{;;L[?sI{p<9>/.Z?gܞG xuwh&R116ypb7x"@~DSc R ҂}0c2;Cd2t@T`b{cz/)WNpI}F{܉CrB3B"/W''Ģ)wz@d1!Ƀj7j~ߧ}{%^l&?O8>:sM<9('_9O'0!&q*x; e\MA}o|'DdٯkuOqjWNBN {HOֶ=iGN:ND*YN:fH120%㖜&))(LiY%6!09tHmJ)*0 5Y,&H\[/`%XqW1/aS]dJO$9,#g@IU}EQ>6Rp=1-=*)h398@9:D>AoE!2=U$p", B *KaE YeijJhUEVNPbbZ e0ՁBW"Huthjh0eJAajp76t; +tP괫X+Ba۪bQENž"L*`²nPħ*{Za!=k&ArlPD,Aq&RQijEA+lJBp]z'!0YtD.)LW3ij .SvIkM_Oh5TzrY B!.u@5!jH:B !vZhE:L :vUQfmcY҃̚s!AD^ U`Drq딺0h$L-.Z::ǸȌ{拺C2/ \dAUeQ$q8N^0pf%z_d!2K:XTPUR$sL!VB' [ԅ?pV1YBExR]tDc^ctB˝ܹKb":wiY, [Su%a`bkL,~-PM :-MġMCnC`?!/[(Z 90|`O96,^c5.QĜ<s^DQσzK^|Z dV44KNJc+2ϓr0Lpc%M*Du J'y¯R[:jLk hVM Gc)UK Z )OL&E^v/$Kc4e 1U;lTl̔ C1chLUjEG#k;OY*S4ErB ;L5O܂c-L'-29S.o`llǥȐ8\XjTT.w7:4X; UEr xtT, fbe,IS3JYN:EŖiG!(H7*AܞȲ"@!JC4K4)!"e8/{;R mT^fc`3ar()r`g22(enJ_Km`(N`I"Ed>%85UL+W 2ĕjep(!\w.C~:ETj;,:>NUPet9,J)F֋U­0\1(/ðWKM]pJXT%xЩ8D@peQh j)%nCiiJT؋+(z^ ܝ&JzX֛ӱB(SiǕf%L3Me:YV: SGFDDFWF$ XSEugnKMJ$B+2K.p0ZӸf2 @Pmj6>e]:[,.5SI@J!cQ E`H"D悋}x.cYA([` L l@h bc8qP" u*lH3 4VEP\97H0xdJ&(P wV*iјj(vF (Z3iirl- eQ$sK؁: L;<2͘&8m[X&``B,6&򦎢R GS* B Y[q*mO%O'%dIV9 8FuYTx 0@5ȊaoՀvSlxK+ Zy:Á"X{uPug0 TV^xT, H$:z 0:us!\b`^ ]g|wk큢Q%F)uR*)jANRL|`XkT^u`y.<*[b***ۃJ+UP <D(d`:Hͫ#PM sBn#\2XH }e<2 Se (.V]g!VHCD̲!1x!Sh ؁LQQN ұn; deؒVZ,sĖВ. (ux U1|қ$9p$(z̲IĻrŒX]'2e9(9$NRU6N> 0U4be*Ͻ`2)k8*NfEp&֡0Z&ͅ,3q6B uc&> d#67|F`"NfJC2LU " 4rZ&G4 cpAl; HF:-}ث &~S$YmXv$ao vٶdL+m^]`bgl+Du'&E2VA4iΒVgm:JR[#Vhac1f Jm aD)E/@{dLj$|üaA`Ջx0 WRY%fCAX Jr&rU)GGdER*%8:yہvzCO0l Tk*oiv;.*ƳYRV*tl)MTﯜU/>61%3<Dǚ~|1~ GCPqjVv$cU^|w(qGO%3vF0A.,6<=>sKT Ԛ&Ru (-TO 4^:^USfϸ'NJcIS= o&x/'`?Q!L+:{Z(&|3 UyHRLYz?%:a& 4VȎqUX|_"U=E"S'l^ yzRf|GUZcȗLO98Xis>͘7wx3~!L%}/GyyǰL%ai&lG4͏i&ӺA J[1|fBl N_i&5OCh&:FKɴl ^Qp} -?_ AБ|B xj A>&SK,ȧ6`5O8N8O8Nd+T8,_TZL&k OՕ =W. 43\ͪ l>1OcnQ5=0g슕e/>Bl9nF!CF5kZt+ TgYwvdA@p[z9p8_Swơ{oPM$o8>FGAL'N1' v؏>HѲt{3{0f|TGtҔ~!tz;m:֕?&ڝSpbڷwR?ڛZzv`GG1#d 3tڇnnV=xo @B?r}*Cm~{KaZ[r[hgq<ÿOsW?nyUpwp @(pݤZq!n9$-\M'[NaÎGv#19⏥/kћ hɌc%X9ז1~1~=Ze R_^ld&sV=> mlUb9I9rK|?R`m6laYs+ɦpɷtvX-vzg$e=*ˣ&CE9Л"Hu2m d73WgWTߍ~u;b؄{totV)a9h {t@q]ܽ <͇L-fgb!{n2CpNNn>BE9Odd# \=}sp6ş!쥘M'Hw ^ 6U3 ʗK3?hl69|WXq:~cޮR<^XwbF_)nS3J}1dJ'q+J-Rk⇼Lc|B+?#5mh, 5-U oJ\:qt:Ke33tSstP?Xq/쇞jIƌ:rGldY3WC2ƑF"܀r۲\ͥ\:^|YT>k9'*ĕVŧH^_cax3seBBbYX$5eMғ5_Uȭ WOe3RCrH˾OHzSW belY6,fA&- (K_wGrZYlHI` ge߮5tF6>BSVm!j%f@$ͤdm޴dHط">{Ϫ"O*u+_,֝nѷ(&3=kH*Iz9`]s﹐k0ӊY& nW9b+,lfbl^jYBez-d=c6Lg6R4-!P0qAx`ο6s,`=zTun 0lrZYvB0OI˵{mk*`~6@0/ucU}FO} ]fQ?ycaDӥ8|%9 L_~_S+U_xhAlOo؛Awk"F`z AS/s9Ӭc[u fʆt~^e/(y wHl٫ku948·ti7$0//*ܥJ`RapîeP.L)WcR75Wkwk3ܝG*vЛCSTj[%p5"J%_ӌo'V1X*277F,Ƚ36oH#;l7$n{Ո̫׍%W/ }??<{|˱}Ŋ{U+Xtؖ$CXj3'z Y@|g<؉o }w ä'/*Qy;`oWv pNX<(=pl;J+@[XkF/Ky>qs2׌Zlg Pܱ;)+5[mBpsxT ZfB;&,*©?ؿд m }vE39&~܉)tۉXHNZ-Bl߻V\󝸶׭*)D˱Y|}#ۉnaE؉-"޷\x m[zJ nW8yc ,ZSirI"Oii\/?8r|A2W=!k7vnp;m[Ӕ^L^' ֠1Q}Thr?r<S}0.],/ ߺX>gm٧֕ػe$=¡Xbnt AXԺYѥr(=eKqv&4>vӇ{gU٭> [?0P5ͫgduq;,l7sICv&S6kOZT3z'ֲړ>O4}/%rov->iQ>Yf EϧQxl94ޕSznsA]M_A-2#}]#Ik] 5OR{M3%{ݩ_3_vjJ߮Lt-睿' C/N|g>|g@>D ;{hzww;}jy;ywSxw;лλyMfu|g:v>{y;y蝇($-lpzox:뺼u9»_3#  [ob_gݺ:g<׾. st.0u)j+"ηtt-?Wkz!om_8mkz {_ݩ:GF3hj?\,}c]Eg 4Пw-~3>Ǜ;;[|]lY#ozMT#d'5qhJ[{_=#Mn~4ƿk(?pF`}pY Ƈ;}BtmQd4;ЪFqYnF ?[H>2pE\$<- 7}sLdϾN]|M1G/Pv.WE ;~D}})̑|da+<:ׅyE;nq¿(҈C$/0\zhgw,^/0חU?|}1}ؘ֙>2u+{\tEQ9!Yt ka^q? sĪ/8;ש}FԧN(uf+zx4WNͧ6I _^TG6"O-ko{o5KJĮ*qӫJJ|[\V+ݗ0u=Xտ&M FJ"T氕ToJE*׆Tnw^ ۵r]o # 2 )4 9e ]w]uo 19i>u9}: TX[d>D)$="<^s?,hKxNy^/r$b{]}϶_GO^KgyW@uaO?{ IO{ o-$sa_\->@~ؿ&a7v^x?>/{{azjwr(ou6iM$Sxm_6ӋK/.N9;@x2ysy| glF2or!sɕY! ќC$2C f39waLl79)~of dp!^p&M~}d7m`{d#Mi'Cܽ ;>͡ݜLS`yLS$r&R1ҽzrL>997O 0Z=Rh.4m =><$ɽp`2Q ) lnȽ ^3Z(9|J؏-A{ WQA8(A;w`]y|f ב}Hrj5I- &M8ܳ"dIqWwI%f598FLxd (')gY"W##cc}`d̓)y >L<@G݃bs1=B2A1ߓ}* IljHE9: `ILT)Zf"dܒؤ"b =%r2 <b\d&&M=EŘ12KArk,+.X*GH2=@E wKJ;M7|<|Lv[kD,DbY *sLF.ǩE&f:q:=gGj֥jqj=Ձx:%*dZ h|E{oޫbGr)v}:~q<k:w -YyGvvU+lYJoRG_irЫ40E₰J"YNJ_7;MJsʌldJ٘%B@+6B9b,6)R"Jˬ^_3WO&tbJ^VKVYeLJƆ$akt4xoř.J3et dH])5)@gpx؅84OMsKQNZcP:2u'1;0 ˲6,JqChm>Zah!kTArlCDXqh&RQmjEA+صJBYE3 餌[@8@P*o% 4M+ԕL3q"S?3`?v0bl4/CҔU|!S-b~J xʥTZQY9PvQzU=nZʌa(JTf FecM墘Lcq1AvbhdaJYRB3pRڱH%Ζsr SF%@/l Xa5@5\1nRG,*e`vG;雊׀A9 I*xs1x &`!L*ހ3 (5:L RUtD-GmZt8~c`0Y4NFtXҴ &MQ95-YS HYAG''RdYZ cTҧcLzvDtE,\A cqK8İ O%iTUkNF'vbI2vbX %&.n'e` P@] *"8N2& 8ӑztVGB£FhL~IL^R] P8&̧:Džx(Ic< 'a< ' #1-kO pN2ꊎ4Dt>p]z'!0YtD.)LW3ij .SvIkMpHh5TzrY B!.u@5!jH:B !vZhE:L :vUQfmcY҃̚s!AD^ U`JjP cz-nr T7sQ]hX"r3#3/N$x+pUH_]TEK͒xo'8eC䖋"Z|,!3k3 9lmXѣjM'icbe5xa2DQVgآ)c 2/“*>_Z'sH[WXZ]j[Kb"X,[uev`fץ`njiiv!vWR|!n~C\UP@ډXSupajϲloI:f`$E,̃`]H0FuAb<59, ScnvHҏa7a cō1jI@fobT&[ʢK _`%TxPX9-i5Nu&˨ R5@24i!m2eR ]]Ӄ׋FQ jF,7&lG2PUv4fBJjϰkd·qTb+ uw*q|Ǝ 'n1@Kצj;yMNۜ)r066RpdHl(E0Pˌ1 OLU Cs1܏ècܱS1Y.!pKG ,0"'F[vL;5)UX$Pl&<]_٨L6YV=i5 p\gcš&%$A\,eXj!'l,L@r&LV%łW=,B^VG岺M `viC,EBwu/RZ2ID֧dZ9&j)y*='h6=@%8gYo]#Jʾ] h2ou#vb%2O&Xu2Gl.aRR'7"1o]Ցآ^Z'L1/Ix= WHby+W,T M17,@#!e¹{fSxi2>6 ĶC4!kn31ه `JޮֈUp0hGMj&0kWm;КAvdf 65Uk &v˶BTXw2iRJQ$c8=D6Z,m̺a[zvH$5i9c"ԶKRDGĨFz<JYi VXE96+*2jQ"VJf;a0PvNY:lxt=z|̨5-Wx 1äAU/͆g]t h*-,r,VBc ,vX,.ۏoP6i4nJ:>xBi\Y(4X6 %`&Kfc),{ BӺ,巳HrWEe[AAن>NJMFcBJ)e;V$UcBu8F㥹cmCek))[S 2cAf2w |(c4vZ΢xb^3RE]$UʔRpJj^fҊ Nmx%7Y<+&\_Slگ)1Y*}2e*5ym6*wކ6ϿT-PcM68uЎ6A h=?ӌÇ&_!CBI_t^4|r' >bŦ! .&ֽA [1gBȯ gĠ gђx{2- ;&-6caǢ&`[ǝGK2%69ÒLH%%XR%j,qq"`$&RsXy‘w±±w&,)[1eäne2)5M)rQX@Ý^ET`#~,NǘyxU#yُ|Jخ ZlPf7:|SNydnY݊>Kw^,j}7'HW_2eLo-Gn_3eLͿfvս{97xƣ)yGS]2JCmkBSx]N0QʫajohlU&fnÃsfd,>Op3]E1T яjV2ڃ-p uyGpodl`d/e>v 9]Aݩ6ٞGÃT{1H QlH':iуŏpa'D:__CEmkBT~uxA0sP+H3UY3s5{o:o6mo6mo6mo6mo6mo6mO`g]25ymkBT8x횉m0]HI!)$FR?6c>>~sm+vuՑνYu8uN?WP>1JsWiV_uKEϸ/rˆ_gKW]ױEYcl,[TYHT}xL#}A GV7^}>iҞ-i;}LJX&TP3T#ߨgJl e'=?͘ona|7>?ǐU%;/mN/IfQփz{G}?v✽3X~j{zTAO^ʰ>?sy|G)P~o6sŜ9sgCUUc=lZiי&,VO6ȎkjcQmֿYKjϲc߬mY'ҦghpB0_UfFzfFIi9BIU`!_?%׾IC(߬i!5?Bii!τ?JM9ïXCC.C)ퟟ~JJicY*#)VPJ_4ZJ?L4UJLҪ{?%7e7BICiKm!?f>0?henhۿo$0aۯ(mM3wJkLMi?N#u;{Kk>տ?է=]۽[9mhϊ ,] }qK^)ѿ:edU?U_nWg_nKC~5wcQ?_>P_B?̓~O*tBN_w?͓x_̻7FCmW?QӼ6ռh5A?|XgkR|۰L꿫hG&O)?i;4|>H-zo?i/;gZ4ӧBJp?n?>W7o?f }_jѷf }?Ӽ?Y_?Y?Yo?Yy((???ȨmkBTx_hNqoԄ"&F\1,߅ZdbDEOjEHh siN=nz9<z?=lAZYvQ__Q^O?eL?eBd+X:'e%{=N\u:5ڤۯ5"CMO/){u_6*/:RA%asL;Upۥc]KvSsl} ?{o5f }d #aŎ?Gl{=s7tBӿ$vߡ?bۨ'mչwGlԸ;ymO?vQ}&/6Ә?B?֣5)ly}/Kߢu `V=)U{#>&}ѿԼF̦ZxE*>w2G{}n oswfߑݧ2'ҿ^} 0ϕI<ܦVyamZz?zw]_Kio@uf-8z!Wt}n uK-\!QNomkBTxx흍) q ĉ8D^>׻gI@XjjgiЃ`0 `0 ?ϟ|:seQ3|ӧO|:2|.};7eGFO6_Qv]T]^ˮg{>pjzkuo{yye?{-x/ D:3D&򈼹e^Hyi#/OGzϪ߯_~ :sMe#M3Y#=2 QЙ[\s=E8}E>GȩT ڲTg-}VfoSVwzV}./>~!?U1<#}=F[ ~QڋBN..+푹^edLo+[\-k dW(}6q$#?z6Bөi?L7!3O_Q}Пuo[=tkȋM!'}/Ƈdr2_Cﲨ: `0 :8o=+8-4}۞cĥXdq{bUq©ήm!ƶg*ΪU\z[GA=^+ru{LV U?)V>ғ)x|Yҁgi\yi^cUo*= !TY?rfgWsʽVn*VX#=Fϫ+[F~yH\L~[O҇h5ݵTow|Sfӟ+);F;:x )/OS yUo2e)Ve3'wgGg=J^`0  ľu kU,Ksؑ5nY,bXw{ w&3QהNQev ]ƷgcH˞i{A3I8hwduwUIWq8I>+@pQşGcZ\ƪUߝ]/:3d;ɫ:gB9R|GW~w2;fzt|+i5nΟgZY|<1NyŬ|E7k?z/k><=Α}N΅>uWydʬdz `0 *\?W8GY:Dgcg< 2+'W6qn؟{ru"wU쏘~c#T?+y{Q,,^qF/Xv8.֩g3}ȸOP ~n%hUG4(_sn|W}Tg&x^c,Fѭ+ <#+}/Uw8BRh_|33!mr\7U9m({ѝpvew[xG]߱?g;,nҽow8]וb?OV=Z_#ve?vN_WrYLo;1g9pV^G~>[_vNOS3 `0Q[ veO\k^8֔v<Zbz\Opbn$~}oz3ј mK vU]^iNWA#x딫jt q :E= z%օq)CcYEqyRG-+u (K\hP'*^ء^q=m=y|Kvūe\rȊ4={W1;=ݷxp;o@>ȘT\Ԏ+C=*ɫ|GJOCW]x1.ﵠ9_Eб Vq)v(ʑ}[GwǺ{-oSdו_˞׃2;iT&w*w:g׭SOsj%Z[~_˯d֮+w]7 `0]kIu+eL]ւoA^;=GR?v쯱;<y o$N1紈=:ߥPVu< <&3KyC/4r)i=*/|Ύ^]QNН1qGw>ù{ ?Kv:A}E:_n+{u=rq͓̳]>>d}+|L01`0 leg:׺񶊝`W,3O?]\9P~[kOWiGc~)-<w.3q}'vuw$Vnv(r52S;Wk_Kϔ8B/hEՠ'9w?K;x:x<|@cϽVyc@ۖSw8Bq]=2lBe6V}eR( VeZT4ade2ޒ+nYBTqSߔ<[&=f[|szP)G}{Zׅ3n7jpWwftEw[ǽ;`l? `0 `0 `{~i`oLy>uoi\qK|}7Svu9G쯿c¾#>,jow{ՆݲL=mW2u_8دjo?kD߱mw>#}E:OۡO;y`$j?tUGmkBTxܡ 1na4n(ޝ~O7yQRicUq Z[aZkx~|߫DsЍ%Q& lg?Ϧ6Mlg?Ϧ6Mlg?? g߳@O޴Yޟ mkBT~x흍8 FSHI!)$FRHnw HYx3ꇤsaaaaxIǏ'U{o_ھgW9 o'GW {>~Jlo߾)*/N\ϱov[iZ_ձaJΝ/:6O- 92b?Tlk%?_21B sY5>:>c=1Ow y^- ڶ,XzusM#גU]>H_yYv!ۉ_mi Rus]Xm_g)YY)m]y,m z1aaaxEߓGקo/Y\k6xjgH|yu.\aæM&wk#ϐ$?]Mo\Ⱦ,/ڥQ@~6s?)}, l gX #vQg Bٙ^uのuhm?}{].~}v_J;xogJY]޳@.)oqC?}>@Xߘ'-(W? źvƔOʙRv[K?[A}?-wmՑ}g\=c}M ggg DŽ-B^k_g?F? v0||؎=ǧHPgs/hؑI t~{n^}ZyD5XWvO)"c0vY Z|~_%/,p\ɹyΰZ/;/xs_9?Pܯ5ݻ\[y|č8gʱL{? 0 0 _k3>z_\S |<)b|7aaaxn.ta?l^Cvkؽ#~e)3<3^kdlc&jK+o"e<.ʞ`^(3zu l+6v<ï k7]/lc[`On}򚄫 G뎱zt^v2)?;Wmr5ocIz?Ozx{&!ez."ѯ 1Gg{+ҏlw<=}GݽFƨ^)zIpG K֜{{e G12ۭqiumf>.}~a? 0 0 [u+7Svq֭y΅ ?ނ}XwŶv?ߩDZۓ-q/?߳=<~#>Fk"qzrQo 9r,nY[;o:)@-`ק-7({߯S@µK9֠ɸ>:n3 _[_*mtcmC>qSL=<6;ǫsaaa{xˌ\ފpx?0׋#5zяc]x^l򼠕(f:~٣^lin59W~\;?vn6erUbS~v^U O7O(|;+SG4|?f*?rW~2oNٟS9~daևmH6mX[J~s.ym4ٶO|Bd/b5ɿyU? 0 0 0 0 0 0 0.P~*1@G\⟿KrKXs2(ߥ纎J8'>X@▼QQbqwx b)_K|v 1M6kee-2Ǜ59?K^E~9ϱQﱮYF8N?~;:=J<-tĒyNAgC \NXKs)'^Kg\~2}6}Գ)n]Or^j~"{p29w6/.z-v:+M{WJYZ굢`% Ҥl9ힶկ#OUz+U?;sd~vND7*.Y+v:ye;8}~|+ÑޅN9}{Bƞ#txխsXɿkSV/uJ=o G<ջL'L:D]6jfgLz/+ؽ[{rCMYq~[{yy czA;w9zszWHVax3 ףmkBT1xաJ`bf5zN=`jo@"fh>06a` o뺫⢛6~[/}|kuba:Ϧ6Mlg?Ϧ6Mlg?Ϧ6Mlg?Ϧ6Mlg?Ϧ6Mlg?Ϧ_(Ʉw/vݏ?x~[s^;_7nfNe(st8g}S<Oᯟ?׶yt$mkBTx_haLjd ZZJB."\hI)͟\XD-WR\X4Y")i(ei7KkWVŊDȿy::w3O}s.91\36c2o*5VrW#?q%n?~Z鯊z*ז.}k7U ?/+qQQ~X;{^.&5Dߘ =Y'zM]1y.kd~f;Qe~3R~7en(oKe(_.pR`"{Zj?-s_kr{&_B6Ō&%qQdr^&&l?%ϣjaoey 죿.ah}go3).#)_le˜C7R71GwB_~nWaBTn߭ h׏k Qɭ!%Wֈ~%F{"??5~oI1ղ߭jFQD\Z[΄?OO=/r쎼}L5%n7nʱW[d35KX}7?u_`rW\%I7f 1ϧ.IɶMp=߻_M5%i^H7kS)i7L9\RLCyZ_׿UL7^yG]*]? |rD 2iTXtXML:com.adobe.xmp Adobe Fireworks CS6 (Macintosh) 2015-05-15T08:26:19Z 2015-05-15T11:35:01Z image/png At IDATx[G`$B"% T"!QILR$e*>oꃥeY/VYZeU4"@1, &&lXvÎݿ̞Ϝ3_Tg=iiCsU.-=[1K@_jL+-E0Vkl}.y}WJ<ϻX?yKt[3Uꉺ=X.7p79 P0|H}<{:{-p~8f۵d}dnodt ٜ~-? 9Q<`}gc?зk{[YF_W[zJVE9ڵcϼo%].S%kPBd`Z:?G> ([pk&/!b$*{Jg‚}ҪjK`0-3bO{./R;Lg3*L*%9+TRxǀGQB?xXt%<_o>[]wR"=@/ (^'1ԁy"a]%%j%_';u~-Ywd~][Aw[,K}7Ўʗa(Qۏy&; lAN7i*?umuוQNUّ!0pH都;]a ƁilAjEuoDx&m~p3qMIXֹxO3 FAcE~={{ "ʈ>r{tHAߦ{s!-Nup ~wnC-:MPw֢v$ͺ׈ 筳ak1j pD"讏=IGgߋoq)[Ta?a`#Q?Xi/@'A0ku9UdSJ{&H!kHل*J}o.SE7ˠFzqo}.{zz`Ơ״sYWA'(pO<\Pe-^E*&gDЧUڋp쭩䵫#:U&h禐2qv"ܾJ{obB2."u&*ܳYv~1 r' KlNًYRtv==JП靨}i|@˞Q"/y7tM UDu?ޠwuT%Q~T4܅zi䣨xIܗN\/R){ý&ӝ]O ڝv7[ꨮ^qpFoF\ĩ;Ϡӿn.m?Hr f%;TGgm(eRQ.04UBF%ZU w ).6Pv=(UHݘxMf:}'9MUIad" ;uJD qQvp }20MݭdF|[JMyѧֺDAj%IwYs?b{O=jDpy>'3 ^}#%FaVS0gmYef7[v~nA?Y,Ĭtr..;xoH(cFh1GZi/&cjzDXA r)xZTOa[f'cȈZ5wKufTwyDc[gI#cPS Z ;)NmMjC<\Wq_ #}^jbRgi]y6&b̯|+膙tn'Q ,_S2y*(3getFyݩ_5:H !*kF h1bNEП<&pj74oxCF8 EyOz]qܜzyLtG;av}3.H+? y}f6܇ak9Nʞ"CHA5+~GUؗ0lf "N?yq/Z=Oq'u3"ۀZq<Ҙ?-VPzxxQ7l4st0? }?kTAj8K)j:p?ƽ)ԏ)gA Q4ۢtG Ej>|D͂'0*tRy9gتI.=8"ymMG"l@-j;N\yҀ^ =<j?rg rʼn?0IENDB`obs-websocket-4.9.0/.github/images/obsws_logo.png000066400000000000000000001622351401107467600220020ustar00rootroot00000000000000PNG  IHDRF&h IDATxxU{@@M((`aײk-v] . otH  @ R8'.ɽg>`9sf;xR r)BPxif gpYɿ; ,GMVq}V{Tv9DDDDJWa{*Ӻhb8R=;i9DDDD9J_EEmnI*~Zhs4*Z|+@--ע6(*]{v# ޺(1H ImdoCClCRY"""J=Yُ'N'2e{_UXgt"""" ( DU @ ^vP-! <K$Q `h%l4'rrr""""7ch`U@W{DD(ŞB<^{W܄I oWV`s7fXgDDDd:V tLbJD@,9ZX""""#1RaV5yJF[WRMDDDdV $: @7S%{k߱ dmj_#4W(O}_?$"""0[h`0*JE">]LDDD1;ުU%㼽UF_~'"""Z-RdfpC+Vu^-"9!"""Z< ѵ(: `(3eddXm۶ŋHKKÅ _/ge!##]%++YY߇XPȷ 44 @BP`Ap [.o72 ;x~e?—DD V2R & HؙSN8y$t8|0NرSK%2%dɒ\ ׯ:ujf͚SGi}#Ӯ4.@zz: .ʕ+#I͛[S4Yx h|R ݻ#iʗ@?x Ô@Z kJ6z1)) +}V`=w@TT)tC N^=B>JDD`h%)xa ٳfc̙HKK3ULn(rUiޣ{s17Z)ǔRgW=R|'Xf ._2UDС ` {)òޕ_.JDD`hQJ5Oh=&#P}=_Sx%똱c"NA`x0-%V""C+]x#$޽}b{W0TRXi UsĀP17Z骔RaGW#1sL|صk-" bݣ5e,<sVvV""C+T%{GFU7߀I&a媕HOO7UDSHcǎEyKxvV""C+.qZHQ)""֭1C54eڰ0=t DD V(dT9GdT5>.SN… qEZE"En@q}Zj`z|g@[wZ AN)Us7=Ugĉ`@z1]c-Ǚx DD f@酤$|xp1ZeZF&oh9m!V""Ck׮>et{Ȩéx猛v[:gZDdaC#Xk`i/xk]JDD`h 2ve7pgꫯay ,AYUo++bxQb@:-xfRӈrK.x'Q^@Gۀ%V""CkPJ57UV^Ė- hoU^nժcyև(дn=9'=TZ N)5޳еOV/[^x4MѢE*#PxՌqIK-͚6s{x dzЀ V""CkRJ-Ƴl2ؾݼ G۷bc[| kƍ^/xU{<4Z H)$nڴ O?46o6o0-l r_~%LgN:"ڴ/"jժCo<@R!2 x7[cfkݻwGXx"#h|rk_\?2- R7nsmذO=4.]b@kD>|Gb k$XFYAY#.tyxC+y5(x@}MJJ $ r u톌 n,Ye˗:"3<>WNZ .t&2xŗ^d˗1jܸ1:`V3gb+H2e?z= 0Z .*i:Mg au?~<>l@M-9Nz뭸tuʔNGBB&2V5ڿ^C˖-^ex.ch%""o0RϿ[Z/ax衇n:Zt}M0p ؒL K")iM'2L뭷Pn]Xx<j DD VQJ TrKe3<>rG1~}Y#A2X&d)Sp~ZH~^dlu <%]"JG<Z-}{9W_Z`Au]uܯK-ŴiӰkh7x>3-JDD`huT ҽnhoTIcƢoHOOW蒌'V"ԩ{=7m었{@z\X d*_X°{nV"hk-`BjpaJB0&{7,45޹}[/ě(aF_~ XKDD`C(J `юEcܸqYZѣFrW [o]s>DDǙ2e*6rCxjtz`* '=Om1C+y0Ob b̽cpםw]5.[ ?+۷:t[oM0HS;@S#BDDz ) @mʝ}ɝ{Z?>8u++^={YU &uR=5""ahA)nbd@표|ЀΝ#DddUװ˖⣏?v5ǡI&&kTWBDDZ\ӪRA˖&/99w/25w۰PNV؅f͚6">|8UʮxhР5kJ>S-M=b2F)um!""rV?QJ*^(0}X'O޽{R:uVhKˇ9sW_9<"`ᄑhժӅ g@[\Ӄ@=zKIIQ]?*7jaÆY,W#VO>Ʈ]n.i`t,x< hVLDD`?RJ4IUVEX+W!C]v ,-[0.ܥsرēRoZ}H)Ud&ԴtIoOBNj@hѢ9b:utͷӂgϞ#"ؾc;7on_3t\rGR^iT4mSOaܟ(oBBB0vXYYYV\9sR%wԅ 0`L4 ""2C؁u Mklgӻwo|7F1Cnk}GR~lݶ]'GD> /{15>JDDt} y*߲Mim[f 5kM69UVŋ}[̘["͛;UJ)L&"" {h»ヌvرc7j׮.]:_s/l'ڵkqIWdu&>8 dVfI oMW+V Ç ^٤Ҍ3d-$"7uÌg\VJ7DDDFah{ U 1cWow@nݮĥ_Zd1223qϚ\zʀvKJ)McMjs hoI! * #A444Ǐ5D?SNȑ#M /(b@;=WA[d\YںUkthe˗aZEDaѢEAbbiR.ADD8V/oZo`mG55UP! u(:uݚ :I"rܶmp7#>.ޤ!P&+"""G1R/g2?Φ"6tzꕣR+Wķ$RimȫAdK(z.!"`КJaޖ=#uӬXR|(hիWGzzeMNNƾ}DTٳ'>ֹRH(?+:dO#R/<~iZ%@~ѱC˖/7|%"deeaĈ裏M e|jn@[chTKS1=#b; h <1]br4,kY98O3Z?Mjժ9,X˖dha{Rm!""҆*R7@M#Lߊ)\0m`:V)57A@VT4':}4.~W""IJJdpm,xI:zANX$Ə[6;qxȈ#'럔RyQ JfBL:N0X5ЧOxYH7cCR쭮 0 ؼyCVBiMOOʾ$"#_}%YSR./ddhUJ<޷o^~e݇u +W^>; YLDtM{{8A2Y^""ԑQZ>}ݺz*|eGujTXDDqU)U cN['$$8qhW;|5:KBLDbbNlA HGZ%F> ʫ>l@(Zhw#4OHJJrU'"" J)!8Bqezѣ9KիW`_u"" dϜvJߌ J)i2pӦMm[u6`TX!3$$4(dڵkRͤn>ݻCaFQTi09F[PJ@DDdD2PJ0^7qO469ZR=`lظAaR%==Zrgӱkn'zV89:@Ǐ>l*^Og&AX*+ʗDDy;׉R8/S#([l#)Z_B+)%K""ʃ'|)))>x݈WVT5uSG=>r5 ;)S&ػ(ON< oMpV""'FZ5l߾ .yȠ UR艈f;Qw//h JF<\46"uV,_\)Нw喎*ݥ FoC)V ZgoG[X#"-~mڼkϼ?H`-SU􉈈|#qO"Ν7KHDD*i>W&)) SG!RhH/Տu$J &"G[dV^E"" ֮Z2o5/^yȠ//gjK]WP1ػȧdٳuwj/x%[ {m9ʪ [d]7h ^|Ih mDDDghGZkBZZo yDժU>["s0 _:W$2Mhj(,_:Ϻ:nш20XW op;/^6=#2"O&5AwDD|==X` rE\o!kԠ^ " ,_~Fm4mDDDЪmU:G#bj uE||)ODiii2eξ-jm%""&_=Wӹ.%)) su8 YcuAKBD'L(IDD:^Ӫ;LFz%^T_uhkn27ÇxbR^bDDDtUZ?4gO@ڵfhoO=: ]/GsMtUpQ]8sJA4DD~qkU.z"b}7y! qihk&Znmh&H1&أ7ٱ &׹gkcMx- ЎZn=fuyPlRLҸM8""ټeU@:7lކpUϩn|{ohknfLD>3g'Z """oF :zM[bCQ8p5du-JDD_" @-^F""vHJTVY%|OGwrvT βokmfZ?Dq[BZZus%\H.HK`7޽{/ ˗a2Ff&󉈂7zu… 8~BBBN'uϞ=w^Lմj͚5f%(uƄ|kӧOVc~©Sp,3guֺYކNےQ&65MG\RǡGRRNV fHܓx{LRP!t銶7EzzoZ&a5;f=ճ[ϕKd+ ǎ $$8wy9kˣF[epO&""Bk{]{-Z#HLLDHŘuh믙 "C{ޕTӂB*:: 2"Į .KdTVBwYǏLo^ ~M ŊE.]d^b"+WFTTUkneO#stg+J;8x vi!dp-tI,5;4JV::Sw:E>ti8qz@XHa(yp޺u+=MAo:VN@lE=";c krؽ{U̍%vN,^~e]muEmBDrZAGmܸgϞq(1y Vأ>2Mq 9]cXb;+#٣=WHu2xlٶbbعk'lقFhl5M|~#"ih65xƌE>А_LMrʡkL7,Z-/9+ZO $צ|:w|< o/vQ$^5\pj`h%" R.@6wIMZ}-Gz]v5jDɾo=GxoQyG}Y%$NdO%믰m6lڴ_j޼6lXasen#""ʽ+ Dd&:[61lsӱM%@wǏ -.R)D ;eiӻŋ[7?n[b۶HJJBժUu41)W| }CDD*;ﲶM9(iT fD@*nӺ ڷkʳNR}˰nRG^p]{5+-- k׮Zogh%" B9XSK`1>CܹzT0yK_[^סd8O#a |24iZf2ҭ[7*\s>g2ඡp,mJ,?{`5Uٮ |\Z0ϱcǰggFF_zC|˕rfZVXѣ.`Ye]:wX6nHܳGס٣[tZ_G+6nAj%hܸ5NRq~ E~X">\p[tn]z1|Yrʅüy/N:e[WO{p-YW|ֳY=/{KN>iӦ믿F…QhQp H A޽@n;E[ku˔.>{+V~;U 'e]흲9{쯹zï{;ltndRmv >!'6mZYX(\Zeӹu W`AL8Ab~ kePTIkvBô<ʃ||I 4u ƒ%Kd`t U*Wí{X'\F|-eɞipI>} ΝÅ4]`$)2%V>.#roB g +`},XE AX(Q%K M;5jxsU=[Q\ B(ӭQrMp *d=/Vz-Wʕ GŬ[bف#8/]Zgٳn]}ܓ}K8B`N\f?;v'N3gvAȯ}"e߾}8tVd@T ժVE2e0p@+f-^ȵWY,V}Y DDFUh}>ףUV> [|k aQl9kz?zmHHH3ŋ$I.1?eEYֽr(%AYQJUԬYAbM"d8~<8vg HؙJbV""*=>>FaL/ƍj*:d5*|+hX :l0kK;|d_Vcccq80vm-#B_F_֩uZ/zeXS %"H-֡E˖:JDdBLBL~k.Mdh}bhب!o`=ȘFmd`nV 75 _}N9!@$O=>U`u#.UJa35X]ѣGl2%aÆVگ֗6fZDVf3?T@g^]Bw&Qi-`ww . ~'HMFP~}.]zUWvѷXlV\[!Ԯ]}Fe:BTwƞ={)nUЎ{^_T#]6~ by ŃaoPX8JDd._L `>k` D>Q)Zj:uo߾tü쵮R'vn,g )%WN;$I*ddnuؾ}c丝lԨ1nUK^FYhD,#/IzkNl2@t/2&]9XHHmd lƍѢy kb]܎ɟ ɗ III=gvPC r_tպ'$:虳f".. 'O )Yުek4jБW2ƩӁYUX^ٳGǡ䋣.Z*ؑ 5QNEFFZuri=޼L͞2u,^]]@*|h߶=zm[+w}m۷W 7fMe˖~ֺSM}.{x7rWsWzN^3q/ g͚0DsYӦ۷\RFFd~>y1 ##dmZ>ʖIy]*ߎYeTjŊؗ/@{lR}8::z)%SuTV_}&LL3*M6Y4 V""szp%g/Ia9{,͟g̕V}ۧ^sdD5†rJN7@2:h ZZoXd%WT>U*W\UZѝZJo{՗~KNNZoq""rFvh-et)6u5nwHmxƝ;wFϞ=`Bl۶ W,WHvڡyȪ\S]͋;wF?SNֽJx^}^jB 9b$>˔]*@DD0](c=zjͫ/^B?RjV8 m$ʾU\CYw"%-_}7o~oyv)))xÇQbEU= r*(4VF^DDLBkqeu⁃|Ц G(돛Df~6- ɴa kcDZfy)Cm5|vCa8p`?{Edu;P^}ءmQAy%u4(eR:Nĉ(Ȝl[UL3QY#$Nl1|lٶ֯䃼rIfj 6gBķ'e[3g8)٫5q:FDD7Xg`%eցTF d[ ׯVFoeGFjeKiRIBÇ\?raPR%ԫ[Ϛ-B֩JPvfWof>=2k0qڶmgi=dԿL2v8_~k/l哸;5kE%Z2:N+Y_{"WPVuV`娫<<67`Ygcسw/vimH ,reYF `72zA|V! .W۷Y{Ja_fW޵{6oC'OqP]9H?&_RQSRyybHHHF"""}:P”쑟ݻ[ӈ9{R6iǩS\deU֥J%Y7%J rWҗ}WQ$ =&*- P.J+*EޤK()\.HUZy{]r{93w_K329>,X}D`=~vwG1>xRG 9Dꍎ?ǏQrR2eȢo4=(:P/ooʚU0;w.ʝ;eϞ]?6oA%ۺ]{8eۧ۷,\ POX{upCg4qOZn=_0دL]yޓ'OGL~4u}39LǏ7/K?+FL7o1 @zqX1sծUmYeaW5uooo;Mv8͘9] 0nkRyoTn]?w!at1;oa7D ӃLC??hސt2t +l޼oX3yLZ իxpa STa#8*!N8,-ԁ 5 @5>m_H7vw$wK+a 3g6\`8._fg9445lG%$>~&'[=.fefZt =v"ѣGhʕ/ VZ3GNe>#4;LZ%H4}t={ހEnwqEEGb4?wnڴ 5jhc4<L3Ҋ7RinQ^z-_@57|rJy*|BB"U hkRrUQ! n2m4ڴy|fl28i8okr4_Bhkfͨ~Jul-5ni&ڲu?46q@uzGU3>͘1Q5({?dV815@6nqMkjYڿq)S ˋ9B[G-7;q~ϧgԬ:eJ3n_TS ΛKQ&ܿYr-̚=V\Z^NUUz& x;vFnF&ӂyJ5@e˗SmDR#f˖ wd\ٳgPFGG:B2ּxbC*<?N)SVYʙ#' dК ֭_7326Ut)`ࡃtIs^m̙ 2Nȓie`ȅ)ȍ=W, 7shLg@adК%bdKׯ_ǽ7oPTt4yyyQ*U9r@ r0%KSU͢/_Nk׭m֯KS&!kBOB+dК7o\$[K^\,jkVz+)9֯_O,2"EP\^(AHh}B] G1WY\S&EmݺUjԷsדi)^CCB|jq" y_oX&>>^_38\H}҇_Bٷ޽ W/%''SIMa,ָv"bqhMrsbCz*≻FFF*@ukhk^% oA~~~.n} D4|لa;^j_˺{n\f 8qpk[yr.?<[L d)4)GΜ3+v^nǏk x+MOF[y]xɒ%]~.}d]"qAJDN 'UVI;<{ڵ ?NW\׶ |򹼳o<Ҷ `Zgqݻwࡃ5|={{N@l޼u͚6ŋr081Ќ 6oL6nIzj}m-d޾hy e"\M 쫆 `pZ``<l! 8qB111:q/ڑV__%~spm@Bk…q} =EGwFEp"ny]k)W\.; T-XBġVP!\;|0_0_7|׮өS'q8w?x yye׵TuSx\LKx󶈪SdXVa|K##~%\vK~A! IDAT[!Z̀C \Iӊ+hu/uΟ֊}ex ׮ WzUD/_ …c?s 0жXo4KfOJy8Z?u^Y2bN`lv㟔MafuK, 7%$&8X9s"r5.,0 hB,ָRDTr '.1 8x/_=[?H8nzjբΓʗ/.r= ],ָk":ADѿ&kbiƵIC]Q͘r>s}:wl{i)Ϝ̚| :8 mw"nb+b-!DaskDԙ8ws {fLrݻWSy׳;ugk"E~,5X:@ 5"Q bkZZpA2rO)\v iY;EcL _>>TgLrp Qp"5uv`Cpp{h=)-[֩2 ו+WΝNC[븲=zT,r W]KW[,%rb39{Fgo?$8yYLc  v@o:c]+˔fLZ9| '~_ؘXYTaW X\AJh-\01͛7kEt+eMLHSOEEE! V;{p͡F9y6ETiN||<-[xFes%&&05k gΞoPpm XMX5ޜQaj5p+zu=9TVM8{ ݿ?K箲g$R}.RKjeZ7 xhy& p N~6YQ&cj W=]qKR\y0[nQ,u5G+_%4azر{&ծ$+Z8Gŋ׻yܾ}FLHi0@!Çt\Bp5>7 v\9VޫU3Q@o)h ܻw @ժU|^prR(֓]Ѹi`]rI-ETԸIc\zJ[c,ߏO K{Y#8~ײf5"b X;wB'MYBilzhfLEի;i?FOӃ$zW˧i!=YSaUw':b.YqDBfXcchQw(|{$&84/YW0AUpB>2߮[^ 5mNdv R {葈R0$ZbC# 葐?n|M!={6SC:)*\Zeo0Ǐ I&FB> >\{t)5er`1|*5"DK+^""jy7"ԕXMNN08z$c#Xy_G݈#U d Bk;QD&QCS94i"#\pNv%nU{f+ 脈*^u%0nlw`l RĬg=q$ݾ}e__pO fv ג`v h_J`6Vzn -00ӛ׳:֮]Jp5)5.,Վ 2kMfv:i&l #G 9ׯZȱ5ƌնR5tS]Ĩ0~ӿ^"(n#">thX.[rl + eɒBBCoÇ] 7Y\U؇kl6}i .`el/ Ek 9еvʔ)#lyϱc-׭gM"5 k\!ڍ d/ PX1V*/yx?4\r5)lҟ(\=m# *֎ v\%q- fJ)BZi5kV\ '1!/$qpUwi7rpU%n&.I9d wgq/Wa+cʟ??URYH?gu*i*ΝH vF"8Cz"zPox} #xQ`Lz"E~AϚ^Uo2K EmV5&:Ff : BpKbȭo0Eȼ===7au[9GK,,kWBfc RaU%TaE+!wc `lٲeshM8x{ySZ@8R'hEkΆk2H k\2.u/)q˖-w᩾Z?ʡՑ _nusΜ55\/C SJV;?+QzCUZ'.\4B3+RzZ9; `0i-wPrr'h6ؘX%  ', 'A7p^ԡ||IӴtCX1E8588XHIIIk.ύ5jƅ#:fthkiMDD|J+Rb:̙S4b)ܼ ! 5*SVL董ʟ}YB>[\-8~Tqfj@p-c{KfHhDU"ܼys'ʑ#C#v9 ?QkVXPbXxZ@:mu㥦' X 2K@pCp$&"vN/o>wǏ)[T+t-DW5Vd#%\Au꿖LUTi98G޼yh+V___\V+_NHwޥ-y]\muz%qp#}!2/;9؎ŅtD'XccdQ 7m  \]@e%ph^⦈a.ieW~[OSÝSdPDlh){Dۮ];ʖ-@:vIIIT#(^ըQ]X9w젋/ u2K/kƌ>FfCko=&dhNXX@:ϟ_S ٳ(_PX&~zS&O=*aE`8OY=Y_fC+[ID:S]vu(H^SմIS o/ozrڽk_z} T"ְ:1`zAX؟8_\ŊE^b ^gW˖),8$ʔp(}6.;3"bJs)\\Z*qc,\%83CvۗMxx8Ix~uzV;J|Q֨ht. pp`k|=nS@;fZ6U83ʣ3D^6?X"5in2,JBC&eF:tvD^z*\t9c!+\` l9unJ.Mku8xJ*U\Go8m۶Oh߾0ZpE`U+dCe"Z(}z:pnWL KJNW_-K8Z=+VÇqydcVpp0~͙̊ CZl"[Gc=?5oei-["???\Ux+2euz*mެUsm1 BD[DmEr*z$WwT@/Ri_K J#R #BF"""FÆ ϯC-leyKl.T^}*QS@V3< =3i\ZR":,hkEzMz[.?E8<,ʗ/zmnCрiFGQ&Vh` 7W{D4Yh٪%+VL!z˺?z\oP ^~`B<"_:B۰ѩKaa5$$XJ =\(\ZuTHp+5k֤Dih\8D݀PHh qp?n5b˅iqeheDno;Qp+K&_.o,իW'pNԷO_W\5:Sq j cF\KF\s܁{Ūk{-ZT!wnP{jي`?fg]6U "|^8Iᄑ vX\Ael,nCg[oz|#pnPBTdIᣬv"e^{@[TA``a:s \&-!  'ʬ\􁋈lp|yQŊms~4`@ p"\Lӷ!Wlc#FӤd֋ ""5D[w ӨѓQV*rh.[,޲ϯSy:t.[&p`m2ܰaWc52Bvp- vBBiFY?CN|J*·I &FȀ,YA(kb"͝=+X\  VsjQU^C!LyԬY3e>? ﵧ@ڹc'Y,{q2lX"F!|BiymOo E!L`TR%eFYx׷@eϞ/vUgϑX\E׀@H+NDGETR{<$)[ )Q(00-ٳ'/)|ڹKigj5,vQ~Σ3g̢da۴v`5’kqW$z$#"X|yҥCZ-*ܴ.^ x 8͗ضmK w vj 85.dЪiMnݺQEpjTA Gɏ+gLX̙3 T>J,6 TYҩSghϞ= TlٲQ߾}FmB`Xl E!  'Ȭ H %ᢏ[? 6P +]Λ@5лwoH 4mteM'<L" U",0:bjn2GZln2(/ʅvM4 (9o-b sW_>@x7n=qCAV`S"j \yU"{pŒVMxϼ FqyhSpzldZ)ڹ k§'$$ЯSCᄑUu/"x}++V6>h6mjժ w۽O NIIQ,Y蝂g̜@5͗utQ%/֖-dj R @p5U4#/y\~ =~(BCC@~5oJ,(P C?Q[n3q5jg ]RU:! qcɬdTiIDDhѢԥKWчPB)RT:԰aC.HHڱc'=zDja_D?o>}wsGffA`@6khXcnjS%u3>%B"]mtϞPbD@:}atUZD{,ê]ugΜY3gy-Z,!ӁN@?j\f WpeBiHxl{"rӂyuht4tH/3vil`H]~ v*;@<%b!H+EDgE4jiVahؠ!*U.#O^:{llo={Q*/߷֮]KVo v(mՠ #!oB TWP&M>Vc@;~"V6>~4JK)mO%D*ک\Ce_ܸnƣH+LD7eu Lm[BZ>( Y8G[ p݌EЪi="GDխ[åNp ~СǏ:YfէO1]jGoz}6M?QU(a.0 h;tTEj \'(`?WQqJ"-zذa//a0|zݻ0jJ0qra@e˕.]:Kp9oHCkmlX(C{: M bƢdh֛OcǍ}X W^)\%~۰qM4޽@E?;SN2-S4{)n ڬ 0 (1:b u.1(Z5M;NDCe ?kլIuTiy={6]xQT7_O *!*Urfp5bk>GZɶCћ3UJ_Wpe˗S0jժO!**a<J`S!u2bƽkv\,\'M$NWڴiCo1j{FFF8|||hD9݂)bT֞cE" 3z:Mmrp]NDRڊf]vqh +^8}ء-{r`ײc~P_cb`}5!9X* CM"$6oчȐ_ʍp`ݽ{7XBjUe˖Rj>r͚)aY,spm97dk\CBe[H"% ?RFYoCq5\ qOt% X8@Pg"!h"UW_O>6mktL4EGGeKxx)SM XW\;'~8*\N*pN?Q)Û:E.j&2D#Gqh?pGߩAJjO ^f ^Zi9/+W2h8@C]JEpj #Fӄ45#V",-Z>@5hЀڵmGW*fɒE<|ڼeRŊji.\3Ϥ?Bvi@p5 }242ӵ D*abM66u߬(mڼ&M$kBS :Hqc$XiS&g@pMppV $2lh4.u'2sΥٳ8<///?SʕYҊ+hat ܚTx 7~MF \S73زy uU'Q9֔Y+< Wyٳg:< /G_6hԇYg_(@&xyyӲKV6tY\FV%*:70 h 5,^ |MYb%D`S$P8.~RHrnZ5MOD2o?jJj׮5j؈=J&4%>l- EEEuki T`lݻwuM_kSE<+0 h: 2H+W+u|nj*L$_|iOZj5\k֮qі-&8Ffͩ[nҦCWHbk&g!@jLZmF2Y R\d&H}oߞխlϘNCN:@EVti'"iO<Һxb}JGx{ySSwZӁ9|?oB4*b݋@UƖ;wnZjjkcN(&:5 Jׁ\atiэ=7jԈ&Lǐ~ZwAuL]Ui:0of2e*^Z βeJ,oߦ~qGI6[p%W5nHP4"ڡCY% zB/Qz?YzRMt!Zbݺ}K̉p7xC}6uؙ<; A+U;ո2ZazNӴEDx=Si2'U>7jӦ yRksФI5sL違 e DGES]ekr{0U_"\f f{"*KDo*￯O8p2 rE5_VFm۴i=RrM7ѭ[h͚5~0?~9/6cLZd)8W#\qjՔ9ڷ_˖.UUh4-)%%c^v}rdsOÆ Ud@)RTfM*W7D2Ȫ7_06oL۷K5Vx[`靂cchQLFBk O}D`Ap<٧5-)))"/|D˖Í,J}ۖlٲo'NЩ'+: ק+^*@%KܹrSU+!#:qXJm۷@]7|D`]r77|SvpeWz/d-"YC}\WfiuJOk "`*,׎tK /Ǿ^u]6Γ]Fǎ3gХKo*hrDzߗ_~gNTGy*UU*Cx(Pg>D`XRΝ; 5mj\Nki׮4o|i5*TWNAAK/Qh .@7n7{$Nx8O<@~…ER)<,\w*^Ysˣw[o)]7;//T"9s:wBϝwKif] 5m}{Nj\N҆Z8~Jt.VUV*T@eѲ16%Jzj޹ݻߋ}> tr`ciTXϬ.^Y[ڛ3gN+G'Oʗ//oߋ>^z]:pY n;رt34^?Q$]ijT}=XT*Ah5.V'JII~V=zPfW<ŶBTbEʛ// K_Oɿ~/$re폿ԿDUyjZs_k֮ѷ9|0YsΪY4P>}裏>R"oz6_ M^zj%Bq!:YJJ "B-\Aiܸq TgjWB t[G' b5˖-+W~K"H:߇l/~'3{ $BpMOmi3ոZ,%%Ń8%~B=\GE@5=C)BKCleHӁ v+d=ڿVX:}9LϜ6egf?n<)X٘c^:oШϓrғ皶*$&:F_Ch5.VHIIID;Xz8NEϟ[ߢ*-,YB͛6S=(1!QjH*jp6U86vuCBq!PJJJV8eUmԶ][}q Ge0o޼ݶM=&9ӣ~=-==ڿd=4_+o3q};"y UVRڵL{v/J3 #\&+R5}XCh5.VKII "XO"#7Q˖--R̆C#r6sֻ_rέ}ޗ_Sj!ORb{7/6=޻Koݦׯ{޿%C b(^mX7SCnӕ+Wp7@`״ kOG}XCCh5.VRRR^'DTDGIw'e3'yx yR,OGɏuܝB5ݯB؂gDH/3g?~ڰaV^ݷ:VPO|*k\JBhu"MӆUl}ŋP"uwF)X҆xmu)+!:ic#%V[i`n:vUVramml٪@5ڵw IDAT"@>V4m}5TGƍG R)з}*qu!DE)\ Bh6Uq>ڸq#*TH2|'(X/_L_՝vة@5+|TSBpx*i RRR8N㬨Rm@_{-к cE Q1ӧN_wǎ+Pk+W^ |)05*X ]v,d^^:r䈙O؃Qe~41YRR\Kin}RRRl~@9qe:u*Ջ?VjYʞ=;9 ǏFݿNNR ԯɋ޴/?ЃLy>̨Λuh2?-ފ>0 ȃZq+7jmW|kӚ 6ȼ֌GE,YB~~~ TF@1w"&$$АCA?Çu<==V ILt uO8B4MK4+"n^/?hhтoN_~fy۠Ah͚5T7 X/_L_5͘>Sj\KjS#6&UaMM>lz)..6h@E֭[ ݺwnʬ{Աc'}kI}R*ܺMk*8y$8B4MFJ*4sL} շqʗ/O6lЛqC7Xd}ڵ:ywʁծ׷=\ BhƯ*7p4ޮb׮]wߓU ÆSdd$5j0III4bHۧ/ݾJL}X̛t`Cpp/i}Mx5PVo+PӡÇ] VϾ)PJr``..]Zj} isc˔yFY+5iD_>~xCUm6IG2U2h`Cp\nolQQy5Fcc?QFH/6L4*Uм6B7 6ʥTEP笳r ܲ73ߏ>W9~r rgg4zK.'w =mJߝ/SNg}&}{ٳG]wtE8A  51@N8',"ȗAڋsM/yqI')8sgiӧIZwv?={ˣ<W3LN@kF{bDk9\'"'CYbٲer} '`TSNG}L̝#Z \dvei F`=sCO+_.<"{c>s={In裏V0*s'J>}eɒ%ҥK@ƪy:#;u)Q~d-X3W'5$qv"b.Ľ2z zPV^-۵c=VWǕo\Rz;*ӁM<1Tvڭ`D&\5nc0 ZCp =;3d谡|r;W@s='|X5>EZN4qQGs^{J Dk9BD>"#{h.?N_wm&V͙'zj`.^={G}`Dc<%8 Qг3DkH98?H2{i<#|O>Yp;O63… S2nxɯ~]uYQ4sƕp#ZCq"r<""yo9Kʐ!C" ȵ7kL䙧{=V+:dvYr VsI/Cak&\7!\ Ȉt("}͇:ȼyOt]O`_7|#KDjN]dY{#8Bƍ+gya vy מW25ByIDO|nݦLRN]9# T e2a_~(b՜]ߧWڴn+q#ʿ#<2}sζ9A~kz@@pq>vTe("/`>dW\!k֬\}9ٶs1S喿HOxJm"rTˬd.e֭2jh5yI$ F%/[[z>Ւ|ȧ(|Yu܄k)- (5ilSTGDmf]͛/#G<)&M>H耊3kp r뭷Oj*ɓ_|Z֯_`Dv`UkY5CI:2vXъpg~*/"7"bu\Ȝ}p؅+3K{Y9sf@RNt{[-Æ 7xSǀ, pfp돲?C+̄+Eb?dHR˅ۘ18S~F /sd⤉2w\7y>B,Us٥Iͤn^[ܗ_~)cF1ʶm ̢J*ɨ1ևV/vƕph9J^Ϻlq{zj4q<(yU_3jAJZJY=s"qT̥3ϐ|Z{=#.;|E g?p@8[DWa_ң Z=yWd駓ڽKsx' 5믿.2j*9I.އ9`Ͱz+ (q̵Ԕd84O>_Wҭyu"4iL?!,,;ZҤIcYfJQc l.0!:q9ޙpc!ZQ&|("=Rs"b>4G8 ҥKevѢE2vX;wlٲ7ŜAKgT/Ě;v+/*^ly?sp(7B.5Ä"2KDJD.D+qe"*J=kͤ X_~!N׭ɯON_ʸ$Dvܩ`ШR0Zj#5 S:3ߐ'G<)A*yA tgeµ,+hE833J-"Mx=?3YFK/I9L. Ye | C;<^PV}%K>#o)e;3X3ֈqDƍ%\&8ysTUia8,"̚kyʕ2={̙3G6l w>N䆉+R*q]7ˬ:jh:eJNSN9Ʀ8Ɔk<z WhE8#*d9_(ի_,D| MW_}U>SCUJg5C|1ll"hE8S"NDk۴37W\!њK,Y"WN?؄٩1ϘbΒ_r%\pA$)/W_yU&8Qs'D7iu8ږ4+~ƕp Zw~ˆGjμR ~~|#sآEqFٴqSzYco\oߞo&PmyđRR%9')?;E}lʓXh<ҹK'[#Vi{.960~x"ZOKMa %>iSܹ_3 =Sr˟'[3:}X 7ɸpջgscF45hD+34aj_:|.G(3קȆC#lkf֩W5p= D+\: "HD8%Γ)OE_|}?=`ZKiRѣF< ъ00K<̃8nn?W7ޒ7x3}f< mp5cc {tK%\bVM ? s4l˖e޼y2c Ytk=!:>g\ W(aesJWg`MȞQG|_O>㸇p4\jWs6{\|VDFe.?k + j"|K/X E1jo5?,i֋TXc0r8PVD+]5LD~3hl""y7 E>9sYsdӦMpA4=p~[ ׺up٫g+ eC"A9߿O FP5gRϛ/DejJun[!\Sǎij@5I`k2*"g2G>e˖WX([~qptqR[L%+YqJh~ȬFD_E^c)**¹'wߓ?GSQfY6J"D/_.+WL؞={Gdp<\ 7=¿tw&d)"?9a_ƍe_XVY#oy"ג1op5gkc dUk͙'"%uLО|Gʺudڵl2Yn}:ZL~x\sնp==?AV w2O&6ޏ3Dp/ӑ~Yr,[Tp/TvW7µ5? W( nhbPPeJKykٲ}Yzl\Ze|p8hԪU(MNQ,\6jq3zV/DdqYs?l̿;m)2u|gsNfTн 0q4\TJ(.~7@e_?dJɱ̏N]:ҥKeKі(.B̬jXDZMƼ .>z@Aey=ID=33-#K6dQ p<\ W7pmD+\6lqGO4s?`O__OҚ_s HټpEX\*\ڕZup?T \D S&ha{a:%ǕFe~8!Q9,3|%";}VksQLk`[CgAjy+ ֈrc^ԧ߅NID2^/-dW ?\jԬakwpucp5 ~cc &\Sǎgc`U.ϔ@^1_"lpzqU8{2c Q7O.ů+70.g4iX@BpDŽimΨ ׵bjm+@BCK^\bT lkµd+!Z7ܧaǵdn[G@BǍy/@vpf?\h;zµ@Bp~&\KVг V 6τE]ě $\_6u1pmب@B͍ym's \)?j-\{K6kjkSD+kx!qmݘv(N%@$hסÆSN+v[c \ *0 IDAT 2W {zIWOD5@"F6pEXp9cͽ1tcpmּ@V"ǍyD6 WEmXmY,\5=BhIn{pCA6N$NhY+=&\gLasF) ׵+T Ҵkգ~(ݻ \KP,\w -WD< :d+BAI6µ[w@07\؜ aau6p]G@sc޿4kժqHx=hQK6ïV(FC9\^\ÂSM ג,&M< P# Pг V(7ڜaQ#\hNJhpcދ+&\gΘis6( ".[c \hGAʥ@tW?WpZ.*9:u4auyM5y8 JAåÆ%\ ={ڣ; @Jlኰ \KFV(7M$\up*RrOCp}7mpL6kV(#-ZzJ*_.EӒH_| 8E޿u_'qѢ,U mΙ qpޣ @AC6$g\o&=w<#r_nIa-sΖ7ԗO^ye6c\+meб}';j6R5+ hrrƵjՋByO>dG!q{^=թpm!\ͥM6y Z"posp59:cj3Ogu\-,vCAq` ƼIkdS#Ǖ\YJ?!u\Քp-YǕp` Y@f VsS0:v&\KƼM+("Z KԄUyH3zgf{cu6mu@7m 5xڷ`;\( u@'̍y/jנza•{\ńslلHmV,\P08 rc+uarqKLzym~YN]lkD2>T1o# @;Æ/\9;~`}@D&xڵ׀1:{ln4\7Mp`?D+(-\>h?a~է?Ƽ{ܘwpȥҥmk W~^gspx9m%j$\p-D6yH}5j9X/~N5(5xLΝ[hs&\i87mϸ Z @ܘ7vZdmލyul+ON:v+ p{`Ͱ5 !\KFЄh9v}ܘbe9smΛ \pe9VPn{)joe4<ʡSζõ- %#Z B ?\d}TTf 5`ג[p` pPf1.ኲ0:gsF>VkV5CCQ;uq%\XA@H ?\_-uj \:CZpݦ`8"hqcW꛻1pR`QmF7B&`ͰRypmHGj;vn+)K` !p?ᇫ{\j5`L.M.9֊  ĈV)?\+;{= 5xnjuX渵kD2^9` !ƼIõ`pc^m?yCf-5ڎp]<"hSܘWۃu_}Uk([yc({: 1jyWÍyD' sEd. +mD+@"`h4\k yYu劕6΄HΜ1uc^ 754O2W5-"Wn̻.0P0@DЄgd0H'H]c5pֵiA4oB}9mMSk6ZDdq,T5[τ+U˛dirzJhJ$B&\ЖrcÃmmpS W#pZ暫 ׀!\Vkp} 1\9nOtD `htW7b;\o%\ H ]vɂ mNV+u&\hk(%:c (V?X}4ڰA#iצ,%UC~ͣvKο|S 8v{%߈\|6ʒ'IYg%[%Wþy;{v%2mqOF `Pϻ1v@h:DkU$Z<H$5XſTxѢEYWH$cl$n̫'"kp=ɸ5`_D+(#F{Wł5ͭmmk:YPH$qO``5T4X3ג1Zs@ %.uYJy( (&,I$EQ[ߵkWN5ą sKp}MPҺ#7`Bdk. 9f;X %:JƼ+sxܘoY`(ȭOܘG*E@iF  ڔplv,cňV5QWfp]h!h ъ0+F@$.6uݪ5ͭm5(P&Ƽ>(Mq#Z `}ּ%X3k8Hwc٪`(ȍn̛E@%uӦM:&\.\hs&\9:Y۸I8A[V?X5;w dfmNγ9JZyYP]1oBԏ&`CAv=ƼS݈VȂD2މ`;jm$n K1&;n."Ƃx@@yC \+CAt,ܘw g\C]y>ARA$1/Sd`ꫯQFeK8#Kժ(7浰9$KEdmlG8zpd?<{XK/W[̼N#}~`{V T`ݵkW0käUmAmwţ|aA?󾩴χ5K$[}sܘ.AeF!X֢E9+VhALPڶBD2>*Jv~ב ڔp V(D2ޅ`p zV8?X5O`RCyVT: H;ov6O$(Bhu!XNAJ$Q@@ xGW_`u^[@ 6DUfsK4_3/7qI$eڶ8{h&QHnwG>m:﹀2 ^zԧh/1oC snzUu]9ã>-&ܘ7#ZD2^_8޿'Zh*pE6Wd K;<Iъ2#Z#\?]/wc"\KPhV('7)"Wٜ?skZFK/Z&D+T޲ $\4IEN%\h "\Ѕp @K֮?\gp m=WaµA YƼ"r9%\&k)ken̛E ׻uk=ED+䀚p`L%4D+䈊p؟pgµgA/"Z6D+ .SLۻn{L&\oP65jc+, /µtV?\.3ԪU wT@@X ׁ|M5V#]ʦF p@wq1@h WtyͷWV+mο ڵkpUhܘpwm= ƶ i׾D2 4 Z@-z- @ ;ndL \VP+l ~m- l"Z@7"\CIO$*"Z@]Q Wuw= W#\JmO ֶOD+(6GHׂ 4(+ ʹ1o6 k~Zµn] \h*׿/ /mٞЇ+ ƼD6GLׂ #Z `ܘ6 EӒ5D+ .J0+ %\][~mMf !\C[PM hr8+{pm}k3TX•hù枘p]6o'Ddθpl{E@H1op0? Oɥ%l"V]Lz˭Jl + !%\ԭ[ ҢyKµV! @?+ r"Z p ={؜+ !ƼYµnõeVBNCߗp$\p%Z WtQrp •h \eUk)!+F@zͽ&\˄M-o=#&\=An̛!\]["|riټx8=}Qµo>+>-=+ %\%\H[bpm{D+DpC=%7=!ZBN&\kC!\V@V!\˄k/9+&\ u\k9(Ȗ-~L$8 ٪IDAT o֏4l+ ؏{\ W(\FV@WtQw"\ńkMdǎ6Ǖp%Ze6gɄk=* @ڇ~( ol!\cCD+ܘ7vׇp$\GR!\%*JJMK^Kp%Ze!\|/۷o9)&\2S Wd۶m6'w.•h |o[g\cD+ܸT]>t~ɧ69?oF*DMZ$pmH6ٜ.:J*LE}pkW+ڵ+7o9 + ?\/9+{pm֤|6g<^hdj:up`;vHmk+ : @?_&\}SRp%Z9%\֫ JrFCߗpsN-DiJr;< I$EdV8٩?9%Cݏ8QD+|K$㗋;L<(IiV.f6(x !ZyCW~^άbH&hf`CK` QEDV5+e p}W#Z:h~qؾR)?@KEd6Gșk>8~s#@D+@wW"gh`vW%"'"93Dr) P0Z@(<D+ HfȽ1Bx"ZAsu G3gX?N`@P#"CD@,a-O&Zap\VuGUyV@؝@bSV " ao9$IENDB`obs-websocket-4.9.0/.github/images/supportclass_logo_blacktext.png000066400000000000000000000175431401107467600254510ustar00rootroot00000000000000PNG  IHDR,HLoȍtEXtSoftwareAdobe ImageReadyqe<&iTXtXML:com.adobe.xmp TIDATx] |Tչ?d2I&dOf @T  Ek[ںԊZžW[۾>ik-jUkՊ (*KB!!@&dd$%ɽw,$~/{s}g!,l6[{Ϟi?g޴&TWUwjr5wGV\|Cj^t:888’T*'h,`/*$,d*h4vKcJ*%Ĥ#oin.x<.$,ZmOIM wwwϰvuMKӷT*t:Ֆe]aKX{r wG.2+1)i z{888 Z_degUѴN׷sqppTbKs8888t!!Ǘ*$nmwww~ټ<%%ejZg"h:;gY,.+233s˔j]1&eꚶvm\\\K/]xt.45E߃9\C.D7+],尚-+>*'h1CH˂;lAyn8Fz --*_qAULQU'O>%#Zg9ȴZ*c))Qufˆai k.ri88ͱpB'NX ERy[[ecu:-1)sXf4OH8FI}VYQ8c_7u3\ Hqq1ZxFnmok?jiY}1Cp8` byӷϧQr:+66v ~VHBI.2:oABB؅?" dU>TQoIɟ_tP\4T.Z91(7 ʨ{c%G!c9988BFX55z%^PFiFcq8Ur7qppa p-5UQ<aKq]ܕ\aedfRH7ͫgZI5eM+Wk\x *X8_cxQQKmXԚ|ϩRP J iHEmjADA 9\j9Njuj9xN*g(Pt= ס%N&_ydN%rKSΠMu ɫvZeN6LXzVKbL&SImMqGLU*Է;bז5w~kVa7R7Rۊ `v-6J_u.PK"^{F *97pvjE3/.Cu6S&Dij~VjP%^!caxй[?e*\ayV: %)~25-38o*W9-j1"t`EpT C#;?^ӟ?aaɳM^ Z O76*FRjw2z)W؎UŐP G$?@E-Rx2="ĦüXA2 3X.k.Q_QD $B~$NcOhUV'޷XRJ Im$enTɕބ؇  -ևk\P{d~\ ˃E=vl'[!0ֹ0m{w>s!!Conܹ-ƾB-T|ˇӧB * !ُS\p|sxY. ~*(3)KTJDs/ELDB*ˑYaVf9)|.w2xJ>Iob.}UCHVYLtRP朽HRȠsPf3ra)RWX܅bVJސPW ?2.OJ 5b˙i%1h1[/:U84H^䖂,t>?,'a(^]/1 @P鿊 xqn2rtp'ց'*f%5Ҽ ;&y*JנËԎF'eLJ<䴟W@\~q %2ruΎJ{>[oٸ믷:6"*€ d3{@˄3H,&Md% qog*S>#ʐ O] aCkņ٨0K _W^!ӁuHXsD.t$\ΑNAWm7>Z|/~UWٲQ9/8c^[S?V!ۀ8;iϹԏ0U 'h0b}]I=טV}Hl.i,N$<ӺFG""5DVPbA}MY?r|To6_Rw iA0Ǿ@3Bigˆ%]c,0BDԕwF2rpTLbB@PmjD0z'N2: d>$aJ͍dxlrg&W5^Uy|Z!a3RFMGB!^(*N%8=;̝dvݗ3 i ݄9D0_/*\w*( 78ӛCxX;"+di'!|EF>&Ã%Cڂ &%QF՟me2ʳ.Ur>ә.8!O'A:ҚkgWi(:ڰs^]OɊ"r ͆w:K2ŞMNprk4Z~Mg=B'h0 "?m,k(وC^ܱ7*.$\" Pt޲|аd56ȋœ;;$\CA[D:g:U}E{S9u\~21 =Ƥ :V4/im^tMh#Q>9 H"犠Ė61"ތ=L"?50!? k=420 }~VL=$>go*2:I/ża KEHw,^;L<h9-ú'eʆ)0i& PAK0ρarlP|D;% a39iQ>*BM*+H/ɟ~fnr%PMwhrЩW $}[*Q%_倏_Le}9'Xua P u'ߓsxV)L  4;,@Nh<.r~0 Gnp]"n le % Jʁf\2q!!%Xs5mSSV *kCjdH{-r]}@Z0?"azo*U oed @0\ j fV/yȕ޴BoŮjbgė2b"$k?Ya^GcԯN"!lډ%IL[i9iFGM-x?#7UQz̋WmX*\ =Ø; /)8PD QLFlKLS0b0Dz!s ‚QtA%YWː0wW&T<6Z*DO,Ǒ^`+VFJ_M$j^TՇ*HzsS \=%SD2e +ʠ^ТR Ƭx;f[y~鈰,XSt뜟OmðpvsX!6(`yw5X6 dW[Ä Dp. [ 4[!Du7yCY;,u$mu^ a,`n7D- &z'wz$3F5|6*6MKn GL]^vԱ\y((_3X<,9;"fkFhHә9 ̸/8ϠI$ , 0Up93_OFfvAg ct*V3| Y yd˘, `naw~!A\f,_8aw?"n[S ǼA蚍2Wb>BF&EXKUP5;]Fbya>Alys^Bfz#.L<ͨPĔJ?Yv F>A'Ʃ2e'3״juy6PUv,aJA)D|ߡar49l#w;a?b>/lE,b>1NiƎj+b:9\ AwJJ7ML%~W<)/nF\۱*$5%xQ'{ 4_A&jgJAz 27&((trOtjɻQbJa-E 0TpHB%#R1nY6ʧkP$ыr\%;{X @I|<ω5cHcR1Bb% l9\{@ kBJ!YE&Zr4ȨFoooNټIɇfXu+ I5Z39#i @Ow飢l /r(<v1<ٌfj*r1> VT0wחla&7ZO%rϧ1y jI`k6udx@_QP9@хmL* q7ǎy W4zC= ,H7Q.c],ȧ~ڟ?0xjaSNb6BzܷyVK㿸zijjYz5yMэ#aD)ezފ$ |;dH硔Bш ŏr'A'=ֻ Cg}AOU2S) Q z?VO E FkxBX4!#l0Zo6@Zߛ3i|mΪ:qtڵQX5-oNˊWܳ 6MVngG*TLj7)(ChBo+ `O^1aW8U@?GSN[IS4B+ȝiYcbPªGI^N8888d$tFR%5iyR3tF Ǹ`UO556hmm];F iĈ>*˔.$!!dc=N|k@P* *{hV\\\9s3ǟǐQLXT]BIg]kKW^uը_<sĉߠ_SVZ[ ' UaOLL]#la988’`) ~}jZpspp%aL&6 b\^bXx-w;G%Ĥ_Shojw=GXHB;88 K|988–vt:;,6_ُP~vo0L[988 0:~IENDB`obs-websocket-4.9.0/.github/pull_request_template.md000066400000000000000000000033451401107467600226070ustar00rootroot00000000000000 ### Description ### Motivation and Context ### How Has This Been Tested? Tested OS(s): ### Types of changes ### Checklist: - [ ] 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. obs-websocket-4.9.0/.github/workflows/000077500000000000000000000000001401107467600176765ustar00rootroot00000000000000obs-websocket-4.9.0/.github/workflows/pr_push.yml000066400000000000000000000445761401107467600221210ustar00rootroot00000000000000name: '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 obs-websocket-4.9.0/.github/workflows/tag_release.yml000066400000000000000000000541631401107467600227050ustar00rootroot00000000000000name: '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 obs-websocket-4.9.0/.gitignore000066400000000000000000000001361401107467600162710ustar00rootroot00000000000000*~ .DS_Store /build/ /build32/ /build64/ /release/ /package/ /installer/Output/ .idea .vscode obs-websocket-4.9.0/.gitmodules000066400000000000000000000003051401107467600164540ustar00rootroot00000000000000[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 obs-websocket-4.9.0/BUILDING.md000066400000000000000000000047761401107467600160360ustar00rootroot00000000000000# Compiling obs-websocket ## Prerequisites 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 : ```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="" -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="" -DCMAKE_INSTALL_PREFIX=/usr .. ``` ## OS X 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 [![Build Status](https://dev.azure.com/Palakis/obs-websocket/_apis/build/status/Palakis.obs-websocket?branchName=4.x-current)](https://dev.azure.com/Palakis/obs-websocket/_build/latest?definitionId=2&branchName=4.x-current) obs-websocket-4.9.0/CI/000077500000000000000000000000001401107467600145745ustar00rootroot00000000000000obs-websocket-4.9.0/CI/build-macos.sh000077500000000000000000000014671401107467600173420ustar00rootroot00000000000000#!/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 obs-websocket-4.9.0/CI/build-ubuntu.sh000077500000000000000000000001571401107467600175550ustar00rootroot00000000000000#!/bin/sh set -ex mkdir build && cd build cmake -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=true .. make -j4 obs-websocket-4.9.0/CI/download-obs-deps.cmd000066400000000000000000000004031401107467600205770ustar00rootroot00000000000000if 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." ) obs-websocket-4.9.0/CI/generate-docs.sh000077500000000000000000000013351401107467600176550ustar00rootroot00000000000000#!/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 echo "-- Documentation successfully generated." if git diff --quiet; then 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 "Azure CI" git config user.email "$COMMIT_AUTHOR_EMAIL" git add ./generated git pull git commit -m "docs(ci): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]" git push -q $GITHUB_REPO obs-websocket-4.9.0/CI/install-build-obs-macos.sh000077500000000000000000000023601401107467600215600ustar00rootroot00000000000000#!/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 obs-websocket-4.9.0/CI/install-dependencies-macos.sh000077500000000000000000000033301401107467600223240ustar00rootroot00000000000000#!/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 obs-websocket-4.9.0/CI/install-dependencies-ubuntu.sh000077500000000000000000000006271401107467600225520ustar00rootroot00000000000000#!/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 obs-websocket-4.9.0/CI/install-qt-win.cmd000066400000000000000000000003571401107467600201510ustar00rootroot00000000000000if 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% obs-websocket-4.9.0/CI/macos/000077500000000000000000000000001401107467600156765ustar00rootroot00000000000000obs-websocket-4.9.0/CI/macos/Brewfile000066400000000000000000000006441401107467600173640ustar00rootroot00000000000000tap "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"obs-websocket-4.9.0/CI/macos/obs-websocket.pkgproj000066400000000000000000000450501401107467600220470ustar00rootroot00000000000000 PROJECT PACKAGE_FILES DEFAULT_INSTALL_LOCATION / HIERARCHY CHILDREN CHILDREN CHILDREN CHILDREN CHILDREN CHILDREN CHILDREN CHILDREN GID 80 PATH ../../build/obs-websocket.so PATH_TYPE 1 PERMISSIONS 493 TYPE 3 UID 0 GID 80 PATH bin PATH_TYPE 0 PERMISSIONS 493 TYPE 2 UID 0 CHILDREN GID 80 PATH ../../data PATH_TYPE 1 PERMISSIONS 493 TYPE 3 UID 0 GID 80 PATH obs-websocket PATH_TYPE 0 PERMISSIONS 493 TYPE 2 UID 0 GID 80 PATH plugins PATH_TYPE 0 PERMISSIONS 493 TYPE 2 UID 0 GID 80 PATH obs-studio PATH_TYPE 0 PERMISSIONS 493 TYPE 2 UID 0 GID 80 PATH Application Support PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Automator PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Documentation PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Extensions PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Filesystems PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Frameworks PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Input Methods PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Internet Plug-Ins PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH LaunchAgents PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH LaunchDaemons PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH PreferencePanes PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Preferences PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 80 PATH Printers PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH PrivilegedHelperTools PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH QuickLook PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH QuickTime PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Screen Savers PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Scripts PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Services PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Widgets PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 GID 0 PATH Library PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN CHILDREN GID 0 PATH Shared PATH_TYPE 0 PERMISSIONS 1023 TYPE 1 UID 0 GID 80 PATH Users PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 GID 0 PATH / PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 PAYLOAD_TYPE 0 VERSION 4 PACKAGE_SCRIPTS RESOURCES PACKAGE_SETTINGS AUTHENTICATION 1 CONCLUSION_ACTION 0 IDENTIFIER fr.palakis.obs-websocket OVERWRITE_PERMISSIONS VERSION 4.9.0 PROJECT_COMMENTS NOTES PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7 IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250 ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE0MDQuMTMiPgo8c3R5bGUg dHlwZT0idGV4dC9jc3MiPgo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5 Pgo8L2JvZHk+CjwvaHRtbD4K PROJECT_SETTINGS BUILD_PATH PATH ../../release PATH_TYPE 1 EXCLUDED_FILES PATTERNS_ARRAY REGULAR_EXPRESSION STRING .DS_Store TYPE 0 PROTECTED PROXY_NAME Remove .DS_Store files PROXY_TOOLTIP Remove ".DS_Store" files created by the Finder. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING .pbdevelopment TYPE 0 PROTECTED PROXY_NAME Remove .pbdevelopment files PROXY_TOOLTIP Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING CVS TYPE 1 REGULAR_EXPRESSION STRING .cvsignore TYPE 0 REGULAR_EXPRESSION STRING .cvspass TYPE 0 REGULAR_EXPRESSION STRING .svn TYPE 1 REGULAR_EXPRESSION STRING .git TYPE 1 REGULAR_EXPRESSION STRING .gitignore TYPE 0 PROTECTED PROXY_NAME Remove SCM metadata PROXY_TOOLTIP Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING classes.nib TYPE 0 REGULAR_EXPRESSION STRING designable.db TYPE 0 REGULAR_EXPRESSION STRING info.nib TYPE 0 PROTECTED PROXY_NAME Optimize nib files PROXY_TOOLTIP Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING Resources Disabled TYPE 1 PROTECTED PROXY_NAME Remove Resources Disabled folders PROXY_TOOLTIP Remove "Resources Disabled" folders. STATE SEPARATOR NAME obs-websocket TYPE 1 VERSION 2 obs-websocket-4.9.0/CI/macos/qt.rb000066400000000000000000000115761401107467600166610ustar00rootroot00000000000000# 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: . # 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 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 #include 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 endobs-websocket-4.9.0/CI/package-macos.sh000077500000000000000000000057371401107467600176420ustar00rootroot00000000000000#!/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" fiobs-websocket-4.9.0/CI/package-ubuntu.sh000077500000000000000000000014101401107467600200420ustar00rootroot00000000000000#!/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/* obs-websocket-4.9.0/CI/package-windows.cmd000066400000000000000000000005261401107467600203470ustar00rootroot00000000000000mkdir package cd package git rev-parse --short HEAD > package-version.txt set /p PackageVersion= "%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" obs-websocket-4.9.0/CI/prepare-windows.cmd000066400000000000000000000013121401107467600204040ustar00rootroot00000000000000mkdir 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" .. obs-websocket-4.9.0/CMakeLists.txt000066400000000000000000000151461401107467600170500ustar00rootroot00000000000000cmake_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) 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(Qt5 REQUIRED COMPONENTS Core Widgets) 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 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 ${obs-websocket_SOURCES} ${obs-websocket_HEADERS}) include_directories( "${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api" ${Qt5Core_INCLUDES} ${Qt5Widgets_INCLUDES} "${CMAKE_SOURCE_DIR}/deps/asio/asio/include" "${CMAKE_SOURCE_DIR}/deps/websocketpp") target_link_libraries(obs-websocket libobs Qt5::Core Qt5::Widgets) # --- End of section --- # --- Windows-specific build settings and tasks --- 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 !") 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") else() set(ARCH_NAME "32bit") set(OBS_BUILDDIR_ARCH "build32") endif() include_directories( "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/UI" ) target_link_libraries(obs-websocket "${OBS_FRONTEND_LIB}") # --- 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 $==1 ( "${CMAKE_COMMAND}" -E make_directory "${RELEASE_DIR}/data/obs-plugins/obs-websocket" "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") COMMAND if $==1 ("${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/data" "${RELEASE_DIR}/data/obs-plugins/obs-websocket") COMMAND if $==1 ("${CMAKE_COMMAND}" -E copy "$" "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") # In Release mode, copy Qt image format plugins COMMAND if $==1 ( "${CMAKE_COMMAND}" -E copy "${QTDIR}/plugins/imageformats/qjpeg.dll" "${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll") COMMAND if $==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 $==1 ( "${CMAKE_COMMAND}" -E make_directory "${RELEASE_DIR}/data/obs-plugins/obs-websocket" "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") COMMAND if $==1 ("${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/data" "${RELEASE_DIR}/data/obs-plugins/obs-websocket") COMMAND if $==1 ("${CMAKE_COMMAND}" -E copy "$" "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") COMMAND if $==1 ("${CMAKE_COMMAND}" -E copy "$" "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") # Copy to obs-studio dev environment for immediate testing COMMAND if $==1 ( "${CMAKE_COMMAND}" -E copy "$" "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/obs-plugins/${ARCH_NAME}") COMMAND if $==1 ( "${CMAKE_COMMAND}" -E copy "$" "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/obs-plugins/${ARCH_NAME}") COMMAND if $==1 ( "${CMAKE_COMMAND}" -E make_directory "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/data/obs-plugins/obs-websocket") COMMAND if $==1 ( "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/data" "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/data/obs-plugins/obs-websocket") ) # --- End of sub-section --- endif() # --- End of section --- # --- Linux-specific build settings and tasks --- if(UNIX AND NOT APPLE) include(GNUInstallDirs) set_target_properties(obs-websocket PROPERTIES PREFIX "") target_link_libraries(obs-websocket obs-frontend-api) file(GLOB locale_files data/locale/*.ini) 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_FULL_DATAROOTDIR}/obs/obs-plugins/obs-websocket/locale") endif() # --- End of section --- # -- OS X specific build settings and tasks -- 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() # -- End of section -- obs-websocket-4.9.0/CONTRIBUTING.md000066400000000000000000000054321401107467600165360ustar00rootroot00000000000000# 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").obs-websocket-4.9.0/LICENSE000066400000000000000000000431751401107467600153200ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {description} Copyright (C) {year} {fullname} 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. {signature of Ty Coon}, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License.obs-websocket-4.9.0/README.md000066400000000000000000000165001401107467600155620ustar00rootroot00000000000000# obs-websocket

WebSockets API for OBS Studio. [![Build Status](https://dev.azure.com/Palakis/obs-websocket/_apis/build/status/Palakis.obs-websocket?branchName=4.x-current)](https://dev.azure.com/Palakis/obs-websocket/_build/latest?definitionId=2&branchName=4.x-current) [![CodeFactor](https://www.codefactor.io/repository/github/palakis/obs-websocket/badge)](https://www.codefactor.io/repository/github/palakis/obs-websocket) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fold_left.svg?style=social&label=Follow%20%40LePalakis)](https://twitter.com/LePalakis) [![Discord](https://img.shields.io/discord/715691013825364120.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/WBaSQ3A) [![Financial Contributors on Open Collective](https://opencollective.com/obs-websocket/all/badge.svg?label=financial+contributors)](https://opencollective.com/obs-websocket) ## Downloads 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 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 - 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). 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 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 **Your help is welcome on translations.** Please join the localization project on [Crowdin](https://crowdin.com/project/obs-websocket) ## Special thanks 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 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](.github/images/supportclass_logo_blacktext.png)](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](.github/images/mediaunit_logo_black.png)](http://www.mediaunit.no/) ## Contributors ### Code Contributors This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. ### Financial Contributors Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/obs-websocket/contribute)] #### Individuals #### 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)] obs-websocket-4.9.0/SSL-TUNNELLING.md000066400000000000000000000032331401107467600167420ustar00rootroot00000000000000# 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. obs-websocket-4.9.0/azure-pipelines.yml000066400000000000000000000117321401107467600201440ustar00rootroot00000000000000variables: 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' obs-websocket-4.9.0/crowdin.yml000066400000000000000000000001251401107467600164670ustar00rootroot00000000000000files: - source: /data/locale/en-US.ini translation: /data/locale/%locale%.ini obs-websocket-4.9.0/data/000077500000000000000000000000001401107467600152125ustar00rootroot00000000000000obs-websocket-4.9.0/data/locale/000077500000000000000000000000001401107467600164515ustar00rootroot00000000000000obs-websocket-4.9.0/data/locale/ar-SA.ini000066400000000000000000000000011401107467600200440ustar00rootroot00000000000000 obs-websocket-4.9.0/data/locale/de-DE.ini000066400000000000000000000027221401107467600200330ustar00rootroot00000000000000OBSWebsocket.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." obs-websocket-4.9.0/data/locale/en-US.ini000066400000000000000000000040451401107467600201040ustar00rootroot00000000000000OBSWebsocket.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.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)" obs-websocket-4.9.0/data/locale/es-ES.ini000066400000000000000000000021421401107467600200650ustar00rootroot00000000000000OBSWebsocket.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." obs-websocket-4.9.0/data/locale/fr-FR.ini000066400000000000000000000021011401107467600200600ustar00rootroot00000000000000OBSWebsocket.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.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é." obs-websocket-4.9.0/data/locale/hi-IN.ini000066400000000000000000000000011401107467600200450ustar00rootroot00000000000000 obs-websocket-4.9.0/data/locale/it-IT.ini000066400000000000000000000030651401107467600201040ustar00rootroot00000000000000OBSWebsocket.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." obs-websocket-4.9.0/data/locale/ja-JP.ini000066400000000000000000000024361401107467600200600ustar00rootroot00000000000000OBSWebsocket.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サーバの通信ポートが変更されました。サーバを再起動しました。" obs-websocket-4.9.0/data/locale/ko-KR.ini000066400000000000000000000032351401107467600201000ustar00rootroot00000000000000OBSWebsocket.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="프로파일 설정에 따라 웹소켓 포트가 변경되었습니다. 서버가 재시작되었습니다." obs-websocket-4.9.0/data/locale/nl-NL.ini000066400000000000000000000031461401107467600200760ustar00rootroot00000000000000OBSWebsocket.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." obs-websocket-4.9.0/data/locale/pl-PL.ini000066400000000000000000000016351401107467600201030ustar00rootroot00000000000000OBSWebsocket.Settings.DialogTitle="Ustawienia serwera WebSockets" OBSWebsocket.Settings.ServerEnable="Włącz serwer WebSockets" OBSWebsocket.Settings.ServerPort="Port serwera" 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." obs-websocket-4.9.0/data/locale/pt-PT.ini000066400000000000000000000030471401107467600201220ustar00rootroot00000000000000OBSWebsocket.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." obs-websocket-4.9.0/data/locale/ru-RU.ini000066400000000000000000000040571401107467600201320ustar00rootroot00000000000000OBSWebsocket.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 изменен в этом профиле. Сервер перезапущен." obs-websocket-4.9.0/data/locale/zh-CN.ini000066400000000000000000000020461401107467600200730ustar00rootroot00000000000000OBSWebsocket.Settings.DialogTitle="WebSockets 服务器设置" 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="此配置文件中启用了 WebSockets 服务器。服务器已启动。" OBSWebsocket.ProfileChanged.Stopped="此配置文件中禁用了 WebSockets 服务器。服务器已停止。" OBSWebsocket.ProfileChanged.Restarted="此配置文件中的 WebSockets 服务器端口已更改。服务器已重新启动。" obs-websocket-4.9.0/data/locale/zh-TW.ini000066400000000000000000000007441401107467600201300ustar00rootroot00000000000000OBSWebsocket.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 伺服器錯誤" obs-websocket-4.9.0/deps/000077500000000000000000000000001401107467600152345ustar00rootroot00000000000000obs-websocket-4.9.0/deps/asio/000077500000000000000000000000001401107467600161675ustar00rootroot00000000000000obs-websocket-4.9.0/deps/websocketpp/000077500000000000000000000000001401107467600175625ustar00rootroot00000000000000obs-websocket-4.9.0/docs/000077500000000000000000000000001401107467600152315ustar00rootroot00000000000000obs-websocket-4.9.0/docs/.editorconfig000066400000000000000000000003301401107467600177020ustar00rootroot00000000000000[*] end_of_line = lf charset = utf-8 indent_style = space indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true [*.md, *.mustache] trim_trailing_whitespace = false insert_final_newline = false obs-websocket-4.9.0/docs/.gitignore000066400000000000000000000000471401107467600172220ustar00rootroot00000000000000node_modules logs *.log npm-debug.log* obs-websocket-4.9.0/docs/.npmrc000066400000000000000000000000231401107467600163440ustar00rootroot00000000000000package-lock=false obs-websocket-4.9.0/docs/README.md000066400000000000000000000004051401107467600165070ustar00rootroot00000000000000## Installation Install node and update npm if necessary. ```sh cd obs-websocket/docs npm install ``` ## Build ```sh # Just extract the comments. npm run comments # Just render the markdown. npm run docs # Do both comments and markdown. npm run build ``` obs-websocket-4.9.0/docs/comments.js000066400000000000000000000063401401107467600174170ustar00rootroot00000000000000const 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. * * @param {String|Array} `files` List of file paths to read from. * @return {Object|Array} Array of `parse-comments` objects. */ const parseFiles = files => { let response = []; files.forEach(file => { const f = fs.readFileSync(file, 'utf8').toString(); response = response.concat(parseComments(f)); }); return response; }; /** * Filters/sorts the results from `parse-comments`. * @param {Object|Array} `comments` Array of `parse-comments` objects. * @return {Object} Filtered comments sorted by `@api` and `@category`. */ 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'; // Remove some unnecessary properties to avoid result differences in travis. comment.comment = undefined; comment.context = undefined; // Create an entry in sorted for the api/category if one does not exist. sorted[comment.api] = sorted[comment.api] || {}; sorted[comment.api][comment.category] = sorted[comment.api][comment.category] || []; // Store the comment in the appropriate api/category. sorted[comment.api][comment.category].push(comment); }); if (errors.length) { throw JSON.stringify(errors, null, 2); } return sorted; }; // 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)); if (!fs.existsSync(config.outDirectory)){ fs.mkdirSync(config.outDirectory); } fs.writeFileSync(path.join(config.outDirectory, 'comments.json'), JSON.stringify(comments, null, 2)); obs-websocket-4.9.0/docs/config.json000066400000000000000000000001641401107467600173720ustar00rootroot00000000000000{ "srcGlob": "./../src/**/*.@(cpp|h)", "srcTemplate": "./protocol.hbs", "outDirectory": "./generated" } obs-websocket-4.9.0/docs/docs.js000066400000000000000000000023671401107467600165270ustar00rootroot00000000000000const 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 }); // Allows pipe characters to be used within markdown tables. handlebars.registerHelper('depipe', (text) => { return typeof text === 'string' ? text.replace('|', '\\|') : text; }); const insertHeader = (text) => { return '\n\n' + text; }; /** * Writes `protocol.md` using `protocol.mustache`. * * @param {Object} `data` Data to assign to the mustache template. */ const generateProtocol = (templatePath, data) => { const template = fs.readFileSync(templatePath).toString(); const generated = handlebars.compile(template)(data); return insertHeader(toc.insert(generated)); }; 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); obs-websocket-4.9.0/docs/generated/000077500000000000000000000000001401107467600171675ustar00rootroot00000000000000obs-websocket-4.9.0/docs/generated/comments.json000066400000000000000000011735421401107467600217240ustar00rootroot00000000000000{ "typedefs": [ { "subheads": [], "typedef": "{Object} `SceneItem` An OBS Scene Item.", "property": [ "{Number} `cy`", "{Number} `cx`", "{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.", "{String} `name` The name of this Scene Item.", "{int} `id` Scene item ID", "{Boolean} `render` Whether or not this Scene Item is set to \"visible\".", "{Boolean} `muted` Whether or not this Scene Item is muted.", "{Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around", "{Number} `source_cx`", "{Number} `source_cy`", "{String} `type` Source type. Value is one of the following: \"input\", \"filter\", \"transition\", \"scene\" or \"unknown\"", "{Number} `volume`", "{Number} `x`", "{Number} `y`", "{String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)", "{Array (optional)} `groupChildren` List of children (if this item is a group)" ], "properties": [ { "type": "Number", "name": "cy", "description": "" }, { "type": "Number", "name": "cx", "description": "" }, { "type": "Number", "name": "alignment", "description": "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." }, { "type": "String", "name": "name", "description": "The name of this Scene Item." }, { "type": "int", "name": "id", "description": "Scene item ID" }, { "type": "Boolean", "name": "render", "description": "Whether or not this Scene Item is set to \"visible\"." }, { "type": "Boolean", "name": "muted", "description": "Whether or not this Scene Item is muted." }, { "type": "Boolean", "name": "locked", "description": "Whether or not this Scene Item is locked and can't be moved around" }, { "type": "Number", "name": "source_cx", "description": "" }, { "type": "Number", "name": "source_cy", "description": "" }, { "type": "String", "name": "type", "description": "Source type. Value is one of the following: \"input\", \"filter\", \"transition\", \"scene\" or \"unknown\"" }, { "type": "Number", "name": "volume", "description": "" }, { "type": "Number", "name": "x", "description": "" }, { "type": "Number", "name": "y", "description": "" }, { "type": "String (optional)", "name": "parentGroupName", "description": "Name of the item's parent (if this item belongs to a group)" }, { "type": "Array (optional)", "name": "groupChildren", "description": "List of children (if this item is a group)" } ], "typedefs": [ { "type": "Object", "name": "SceneItem", "description": "An OBS Scene Item." } ], "name": "", "heading": { "level": 2, "text": "" }, "examples": [] }, { "subheads": [], "typedef": "{Object} `SceneItemTransform`", "property": [ "{double} `position.x` The x position of the scene item from the left.", "{double} `position.y` The y position of the scene item from the top.", "{int} `position.alignment` The point on the scene item that the item is manipulated from.", "{double} `rotation` The clockwise rotation of the scene item in degrees around the point of alignment.", "{double} `scale.x` The x-scale factor of the scene item.", "{double} `scale.y` The y-scale factor of the scene item.", "{int} `crop.top` The number of pixels cropped off the top of the scene item before scaling.", "{int} `crop.right` The number of pixels cropped off the right of the scene item before scaling.", "{int} `crop.bottom` The number of pixels cropped off the bottom of the scene item before scaling.", "{int} `crop.left` The number of pixels cropped off the left of the scene item before scaling.", "{bool} `visible` If the scene item is visible.", "{bool} `locked` If the scene item is locked in position.", "{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\".", "{int} `bounds.alignment` Alignment of the bounding box.", "{double} `bounds.x` Width of the bounding box.", "{double} `bounds.y` Height of the bounding box.", "{int} `sourceWidth` Base width (without scaling) of the source", "{int} `sourceHeight` Base source (without scaling) of the source", "{double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)", "{double} `height` Scene item height (base source height multiplied by the vertical scaling factor)", "{String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)", "{Array (optional)} `groupChildren` List of children (if this item is a group)" ], "properties": [ { "type": "double", "name": "position.x", "description": "The x position of the scene item from the left." }, { "type": "double", "name": "position.y", "description": "The y position of the scene item from the top." }, { "type": "int", "name": "position.alignment", "description": "The point on the scene item that the item is manipulated from." }, { "type": "double", "name": "rotation", "description": "The clockwise rotation of the scene item in degrees around the point of alignment." }, { "type": "double", "name": "scale.x", "description": "The x-scale factor of the scene item." }, { "type": "double", "name": "scale.y", "description": "The y-scale factor of the scene item." }, { "type": "int", "name": "crop.top", "description": "The number of pixels cropped off the top of the scene item before scaling." }, { "type": "int", "name": "crop.right", "description": "The number of pixels cropped off the right of the scene item before scaling." }, { "type": "int", "name": "crop.bottom", "description": "The number of pixels cropped off the bottom of the scene item before scaling." }, { "type": "int", "name": "crop.left", "description": "The number of pixels cropped off the left of the scene item before scaling." }, { "type": "bool", "name": "visible", "description": "If the scene item is visible." }, { "type": "bool", "name": "locked", "description": "If the scene item is locked in position." }, { "type": "String", "name": "bounds.type", "description": "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\"." }, { "type": "int", "name": "bounds.alignment", "description": "Alignment of the bounding box." }, { "type": "double", "name": "bounds.x", "description": "Width of the bounding box." }, { "type": "double", "name": "bounds.y", "description": "Height of the bounding box." }, { "type": "int", "name": "sourceWidth", "description": "Base width (without scaling) of the source" }, { "type": "int", "name": "sourceHeight", "description": "Base source (without scaling) of the source" }, { "type": "double", "name": "width", "description": "Scene item width (base source width multiplied by the horizontal scaling factor)" }, { "type": "double", "name": "height", "description": "Scene item height (base source height multiplied by the vertical scaling factor)" }, { "type": "String (optional)", "name": "parentGroupName", "description": "Name of the item's parent (if this item belongs to a group)" }, { "type": "Array (optional)", "name": "groupChildren", "description": "List of children (if this item is a group)" } ], "typedefs": [ { "type": "Object", "name": "SceneItemTransform", "description": "" } ], "name": "", "heading": { "level": 2, "text": "" }, "examples": [] }, { "subheads": [], "typedef": "{Object} `OBSStats`", "property": [ "{double} `fps` Current framerate.", "{int} `render-total-frames` Number of frames rendered", "{int} `render-missed-frames` Number of frames missed due to rendering lag", "{int} `output-total-frames` Number of frames outputted", "{int} `output-skipped-frames` Number of frames skipped due to encoding lag", "{double} `average-frame-time` Average frame render time (in milliseconds)", "{double} `cpu-usage` Current CPU usage (percentage)", "{double} `memory-usage` Current RAM usage (in megabytes)", "{double} `free-disk-space` Free recording disk space (in megabytes)" ], "properties": [ { "type": "double", "name": "fps", "description": "Current framerate." }, { "type": "int", "name": "render-total-frames", "description": "Number of frames rendered" }, { "type": "int", "name": "render-missed-frames", "description": "Number of frames missed due to rendering lag" }, { "type": "int", "name": "output-total-frames", "description": "Number of frames outputted" }, { "type": "int", "name": "output-skipped-frames", "description": "Number of frames skipped due to encoding lag" }, { "type": "double", "name": "average-frame-time", "description": "Average frame render time (in milliseconds)" }, { "type": "double", "name": "cpu-usage", "description": "Current CPU usage (percentage)" }, { "type": "double", "name": "memory-usage", "description": "Current RAM usage (in megabytes)" }, { "type": "double", "name": "free-disk-space", "description": "Free recording disk space (in megabytes)" } ], "typedefs": [ { "type": "Object", "name": "OBSStats", "description": "" } ], "name": "", "heading": { "level": 2, "text": "" }, "examples": [] }, { "subheads": [], "typedef": "{Object} `Output`", "property": [ "{String} `name` Output name", "{String} `type` Output type/kind", "{int} `width` Video output width", "{int} `height` Video output height", "{Object} `flags` Output flags", "{int} `flags.rawValue` Raw flags value", "{boolean} `flags.audio` Output uses audio", "{boolean} `flags.video` Output uses video", "{boolean} `flags.encoded` Output is encoded", "{boolean} `flags.multiTrack` Output uses several audio tracks", "{boolean} `flags.service` Output uses a service", "{Object} `settings` Output name", "{boolean} `active` Output status (active or not)", "{boolean} `reconnecting` Output reconnection status (reconnecting or not)", "{double} `congestion` Output congestion", "{int} `totalFrames` Number of frames sent", "{int} `droppedFrames` Number of frames dropped", "{int} `totalBytes` Total bytes sent" ], "properties": [ { "type": "String", "name": "name", "description": "Output name" }, { "type": "String", "name": "type", "description": "Output type/kind" }, { "type": "int", "name": "width", "description": "Video output width" }, { "type": "int", "name": "height", "description": "Video output height" }, { "type": "Object", "name": "flags", "description": "Output flags" }, { "type": "int", "name": "flags.rawValue", "description": "Raw flags value" }, { "type": "boolean", "name": "flags.audio", "description": "Output uses audio" }, { "type": "boolean", "name": "flags.video", "description": "Output uses video" }, { "type": "boolean", "name": "flags.encoded", "description": "Output is encoded" }, { "type": "boolean", "name": "flags.multiTrack", "description": "Output uses several audio tracks" }, { "type": "boolean", "name": "flags.service", "description": "Output uses a service" }, { "type": "Object", "name": "settings", "description": "Output name" }, { "type": "boolean", "name": "active", "description": "Output status (active or not)" }, { "type": "boolean", "name": "reconnecting", "description": "Output reconnection status (reconnecting or not)" }, { "type": "double", "name": "congestion", "description": "Output congestion" }, { "type": "int", "name": "totalFrames", "description": "Number of frames sent" }, { "type": "int", "name": "droppedFrames", "description": "Number of frames dropped" }, { "type": "int", "name": "totalBytes", "description": "Total bytes sent" } ], "typedefs": [ { "type": "Object", "name": "Output", "description": "" } ], "name": "", "heading": { "level": 2, "text": "" }, "examples": [] }, { "subheads": [], "typedef": "{Object} `ScenesCollection`", "property": "{String} `sc-name` Name of the scene collection", "properties": [ { "type": "String", "name": "sc-name", "description": "Name of the scene collection" } ], "typedefs": [ { "type": "Object", "name": "ScenesCollection", "description": "" } ], "name": "", "heading": { "level": 2, "text": "" }, "examples": [] }, { "subheads": [], "typedef": "{Object} `Scene`", "property": [ "{String} `name` Name of the currently active scene.", "{Array} `sources` Ordered list of the current scene's source items." ], "properties": [ { "type": "String", "name": "name", "description": "Name of the currently active scene." }, { "type": "Array", "name": "sources", "description": "Ordered list of the current scene's source items." } ], "typedefs": [ { "type": "Object", "name": "Scene", "description": "" } ], "name": "", "heading": { "level": 2, "text": "" }, "examples": [] } ], "events": { "scenes": [ { "subheads": [], "description": "Indicates a scene change.", "return": [ "{String} `scene-name` The new scene.", "{Array} `sources` List of scene items in the new scene. Same specification as [`GetCurrentScene`](#getcurrentscene)." ], "api": "events", "name": "SwitchScenes", "category": "scenes", "since": "0.3", "returns": [ { "type": "String", "name": "scene-name", "description": "The new scene." }, { "type": "Array", "name": "sources", "description": "List of scene items in the new scene. Same specification as [`GetCurrentScene`](#getcurrentscene)." } ], "names": [ { "name": "", "description": "SwitchScenes" } ], "categories": [ { "name": "", "description": "scenes" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "SwitchScenes" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "\n\nNote: This event is not fired when the scenes are reordered.", "return": "{Array} `scenes` Scenes list.", "api": "events", "name": "ScenesChanged", "category": "scenes", "since": "0.3", "returns": [ { "type": "Array", "name": "scenes", "description": "Scenes list." } ], "names": [ { "name": "", "description": "ScenesChanged" } ], "categories": [ { "name": "", "description": "scenes" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "ScenesChanged" }, "lead": "The scene list has been modified. Scenes have been added, removed, or renamed.", "type": "class", "examples": [] }, { "subheads": [], "description": "Triggered when switching to another scene collection or when renaming the current scene collection.", "return": "{String} `sceneCollection` Name of the new current scene collection.", "api": "events", "name": "SceneCollectionChanged", "category": "scenes", "since": "4.0.0", "returns": [ { "type": "String", "name": "sceneCollection", "description": "Name of the new current scene collection." } ], "names": [ { "name": "", "description": "SceneCollectionChanged" } ], "categories": [ { "name": "", "description": "scenes" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "SceneCollectionChanged" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Triggered when a scene collection is created, added, renamed, or removed.", "return": [ "{Array} `sceneCollections` Scene collections list.", "{String} `sceneCollections.*.name` Scene collection name." ], "api": "events", "name": "SceneCollectionListChanged", "category": "scenes", "since": "4.0.0", "returns": [ { "type": "Array", "name": "sceneCollections", "description": "Scene collections list." }, { "type": "String", "name": "sceneCollections.*.name", "description": "Scene collection name." } ], "names": [ { "name": "", "description": "SceneCollectionListChanged" } ], "categories": [ { "name": "", "description": "scenes" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "SceneCollectionListChanged" }, "lead": "", "type": "class", "examples": [] } ], "transitions": [ { "subheads": [], "description": "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", "returns": [ { "type": "String", "name": "transition-name", "description": "The name of the new active transition." } ], "names": [ { "name": "", "description": "SwitchTransition" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "SwitchTransition" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "The list of available transitions has been modified.\nTransitions have been added, removed, or renamed.", "return": [ "{Array} `transitions` Transitions list.", "{String} `transitions.*.name` Transition name." ], "api": "events", "name": "TransitionListChanged", "category": "transitions", "since": "4.0.0", "returns": [ { "type": "Array", "name": "transitions", "description": "Transitions list." }, { "type": "String", "name": "transitions.*.name", "description": "Transition name." } ], "names": [ { "name": "", "description": "TransitionListChanged" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "TransitionListChanged" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "The active transition duration has been changed.", "return": "{int} `new-duration` New transition duration.", "api": "events", "name": "TransitionDurationChanged", "category": "transitions", "since": "4.0.0", "returns": [ { "type": "int", "name": "new-duration", "description": "New transition duration." } ], "names": [ { "name": "", "description": "TransitionDurationChanged" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "TransitionDurationChanged" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A transition (other than \"cut\") has begun.", "return": [ "{String} `name` Transition name.", "{String} `type` Transition type.", "{int} `duration` Transition duration (in milliseconds). Will be -1 for any transition with a fixed duration, such as a Stinger, due to limitations of the OBS API.", "{String} `from-scene` Source scene of the transition", "{String} `to-scene` Destination scene of the transition" ], "api": "events", "name": "TransitionBegin", "category": "transitions", "since": "4.0.0", "returns": [ { "type": "String", "name": "name", "description": "Transition name." }, { "type": "String", "name": "type", "description": "Transition type." }, { "type": "int", "name": "duration", "description": "Transition duration (in milliseconds). Will be -1 for any transition with a fixed duration, such as a Stinger, due to limitations of the OBS API." }, { "type": "String", "name": "from-scene", "description": "Source scene of the transition" }, { "type": "String", "name": "to-scene", "description": "Destination scene of the transition" } ], "names": [ { "name": "", "description": "TransitionBegin" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "TransitionBegin" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A transition (other than \"cut\") has ended.\nNote: The `from-scene` field is not available in TransitionEnd.", "return": [ "{String} `name` Transition name.", "{String} `type` Transition type.", "{int} `duration` Transition duration (in milliseconds).", "{String} `to-scene` Destination scene of the transition" ], "api": "events", "name": "TransitionEnd", "category": "transitions", "since": "4.8.0", "returns": [ { "type": "String", "name": "name", "description": "Transition name." }, { "type": "String", "name": "type", "description": "Transition type." }, { "type": "int", "name": "duration", "description": "Transition duration (in milliseconds)." }, { "type": "String", "name": "to-scene", "description": "Destination scene of the transition" } ], "names": [ { "name": "", "description": "TransitionEnd" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "4.8.0" } ], "heading": { "level": 2, "text": "TransitionEnd" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A stinger transition has finished playing its video.", "return": [ "{String} `name` Transition name.", "{String} `type` Transition type.", "{int} `duration` Transition duration (in milliseconds).", "{String} `from-scene` Source scene of the transition", "{String} `to-scene` Destination scene of the transition" ], "api": "events", "name": "TransitionVideoEnd", "category": "transitions", "since": "4.8.0", "returns": [ { "type": "String", "name": "name", "description": "Transition name." }, { "type": "String", "name": "type", "description": "Transition type." }, { "type": "int", "name": "duration", "description": "Transition duration (in milliseconds)." }, { "type": "String", "name": "from-scene", "description": "Source scene of the transition" }, { "type": "String", "name": "to-scene", "description": "Destination scene of the transition" } ], "names": [ { "name": "", "description": "TransitionVideoEnd" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "4.8.0" } ], "heading": { "level": 2, "text": "TransitionVideoEnd" }, "lead": "", "type": "class", "examples": [] } ], "profiles": [ { "subheads": [], "description": "Triggered when switching to another profile or when renaming the current profile.", "return": "{String} `profile` Name of the new current profile.", "api": "events", "name": "ProfileChanged", "category": "profiles", "since": "4.0.0", "returns": [ { "type": "String", "name": "profile", "description": "Name of the new current profile." } ], "names": [ { "name": "", "description": "ProfileChanged" } ], "categories": [ { "name": "", "description": "profiles" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "ProfileChanged" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Triggered when a profile is created, added, renamed, or removed.", "return": [ "{Array} `profiles` Profiles list.", "{String} `profiles.*.name` Profile name." ], "api": "events", "name": "ProfileListChanged", "category": "profiles", "since": "4.0.0", "returns": [ { "type": "Array", "name": "profiles", "description": "Profiles list." }, { "type": "String", "name": "profiles.*.name", "description": "Profile name." } ], "names": [ { "name": "", "description": "ProfileListChanged" } ], "categories": [ { "name": "", "description": "profiles" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "ProfileListChanged" }, "lead": "", "type": "class", "examples": [] } ], "streaming": [ { "subheads": [], "description": "A request to start streaming has been issued.", "return": "{boolean} `preview-only` Always false (retrocompatibility).", "api": "events", "name": "StreamStarting", "category": "streaming", "since": "0.3", "returns": [ { "type": "boolean", "name": "preview-only", "description": "Always false (retrocompatibility)." } ], "names": [ { "name": "", "description": "StreamStarting" } ], "categories": [ { "name": "", "description": "streaming" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "StreamStarting" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Streaming started successfully.", "api": "events", "name": "StreamStarted", "category": "streaming", "since": "0.3", "names": [ { "name": "", "description": "StreamStarted" } ], "categories": [ { "name": "", "description": "streaming" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "StreamStarted" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A request to stop streaming has been issued.", "return": "{boolean} `preview-only` Always false (retrocompatibility).", "api": "events", "name": "StreamStopping", "category": "streaming", "since": "0.3", "returns": [ { "type": "boolean", "name": "preview-only", "description": "Always false (retrocompatibility)." } ], "names": [ { "name": "", "description": "StreamStopping" } ], "categories": [ { "name": "", "description": "streaming" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "StreamStopping" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Streaming stopped successfully.", "api": "events", "name": "StreamStopped", "category": "streaming", "since": "0.3", "names": [ { "name": "", "description": "StreamStopped" } ], "categories": [ { "name": "", "description": "streaming" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "StreamStopped" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Emitted every 2 seconds when stream is active.", "return": [ "{boolean} `streaming` Current streaming state.", "{boolean} `recording` Current recording state.", "{boolean} `replay-buffer-active` Replay Buffer status", "{int} `bytes-per-sec` Amount of data per second (in bytes) transmitted by the stream encoder.", "{int} `kbits-per-sec` Amount of data per second (in kilobits) transmitted by the stream encoder.", "{double} `strain` Percentage of dropped frames.", "{int} `total-stream-time` Total time (in seconds) since the stream started.", "{int} `num-total-frames` Total number of frames transmitted since the stream started.", "{int} `num-dropped-frames` Number of frames dropped by the encoder since the stream started.", "{double} `fps` Current framerate.", "{int} `render-total-frames` Number of frames rendered", "{int} `render-missed-frames` Number of frames missed due to rendering lag", "{int} `output-total-frames` Number of frames outputted", "{int} `output-skipped-frames` Number of frames skipped due to encoding lag", "{double} `average-frame-time` Average frame time (in milliseconds)", "{double} `cpu-usage` Current CPU usage (percentage)", "{double} `memory-usage` Current RAM usage (in megabytes)", "{double} `free-disk-space` Free recording disk space (in megabytes)", "{boolean} `preview-only` Always false (retrocompatibility)." ], "api": "events", "name": "StreamStatus", "category": "streaming", "since": "0.3", "returns": [ { "type": "boolean", "name": "streaming", "description": "Current streaming state." }, { "type": "boolean", "name": "recording", "description": "Current recording state." }, { "type": "boolean", "name": "replay-buffer-active", "description": "Replay Buffer status" }, { "type": "int", "name": "bytes-per-sec", "description": "Amount of data per second (in bytes) transmitted by the stream encoder." }, { "type": "int", "name": "kbits-per-sec", "description": "Amount of data per second (in kilobits) transmitted by the stream encoder." }, { "type": "double", "name": "strain", "description": "Percentage of dropped frames." }, { "type": "int", "name": "total-stream-time", "description": "Total time (in seconds) since the stream started." }, { "type": "int", "name": "num-total-frames", "description": "Total number of frames transmitted since the stream started." }, { "type": "int", "name": "num-dropped-frames", "description": "Number of frames dropped by the encoder since the stream started." }, { "type": "double", "name": "fps", "description": "Current framerate." }, { "type": "int", "name": "render-total-frames", "description": "Number of frames rendered" }, { "type": "int", "name": "render-missed-frames", "description": "Number of frames missed due to rendering lag" }, { "type": "int", "name": "output-total-frames", "description": "Number of frames outputted" }, { "type": "int", "name": "output-skipped-frames", "description": "Number of frames skipped due to encoding lag" }, { "type": "double", "name": "average-frame-time", "description": "Average frame time (in milliseconds)" }, { "type": "double", "name": "cpu-usage", "description": "Current CPU usage (percentage)" }, { "type": "double", "name": "memory-usage", "description": "Current RAM usage (in megabytes)" }, { "type": "double", "name": "free-disk-space", "description": "Free recording disk space (in megabytes)" }, { "type": "boolean", "name": "preview-only", "description": "Always false (retrocompatibility)." } ], "names": [ { "name": "", "description": "StreamStatus" } ], "categories": [ { "name": "", "description": "streaming" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "StreamStatus" }, "lead": "", "type": "class", "examples": [] } ], "recording": [ { "subheads": [], "description": "\n\nNote: `recordingFilename` is not provided in this event because this information\nis not available at the time this event is emitted.", "api": "events", "name": "RecordingStarting", "category": "recording", "since": "0.3", "names": [ { "name": "", "description": "RecordingStarting" } ], "categories": [ { "name": "", "description": "recording" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "RecordingStarting" }, "lead": "A request to start recording has been issued.", "type": "class", "examples": [] }, { "subheads": [], "description": "Recording started successfully.", "return": "{String} `recordingFilename` Absolute path to the file of the current recording.", "api": "events", "name": "RecordingStarted", "category": "recording", "since": "0.3", "returns": [ { "type": "String", "name": "recordingFilename", "description": "Absolute path to the file of the current recording." } ], "names": [ { "name": "", "description": "RecordingStarted" } ], "categories": [ { "name": "", "description": "recording" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "RecordingStarted" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A request to stop recording has been issued.", "return": "{String} `recordingFilename` Absolute path to the file of the current recording.", "api": "events", "name": "RecordingStopping", "category": "recording", "since": "0.3", "returns": [ { "type": "String", "name": "recordingFilename", "description": "Absolute path to the file of the current recording." } ], "names": [ { "name": "", "description": "RecordingStopping" } ], "categories": [ { "name": "", "description": "recording" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "RecordingStopping" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Recording stopped successfully.", "return": "{String} `recordingFilename` Absolute path to the file of the current recording.", "api": "events", "name": "RecordingStopped", "category": "recording", "since": "0.3", "returns": [ { "type": "String", "name": "recordingFilename", "description": "Absolute path to the file of the current recording." } ], "names": [ { "name": "", "description": "RecordingStopped" } ], "categories": [ { "name": "", "description": "recording" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "RecordingStopped" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Current recording paused", "api": "events", "name": "RecordingPaused", "category": "recording", "since": "4.7.0", "names": [ { "name": "", "description": "RecordingPaused" } ], "categories": [ { "name": "", "description": "recording" } ], "sinces": [ { "name": "", "description": "4.7.0" } ], "heading": { "level": 2, "text": "RecordingPaused" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Current recording resumed", "api": "events", "name": "RecordingResumed", "category": "recording", "since": "4.7.0", "names": [ { "name": "", "description": "RecordingResumed" } ], "categories": [ { "name": "", "description": "recording" } ], "sinces": [ { "name": "", "description": "4.7.0" } ], "heading": { "level": 2, "text": "RecordingResumed" }, "lead": "", "type": "class", "examples": [] } ], "replay buffer": [ { "subheads": [], "description": "A request to start the replay buffer has been issued.", "api": "events", "name": "ReplayStarting", "category": "replay buffer", "since": "4.2.0", "names": [ { "name": "", "description": "ReplayStarting" } ], "categories": [ { "name": "", "description": "replay buffer" } ], "sinces": [ { "name": "", "description": "4.2.0" } ], "heading": { "level": 2, "text": "ReplayStarting" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Replay Buffer started successfully", "api": "events", "name": "ReplayStarted", "category": "replay buffer", "since": "4.2.0", "names": [ { "name": "", "description": "ReplayStarted" } ], "categories": [ { "name": "", "description": "replay buffer" } ], "sinces": [ { "name": "", "description": "4.2.0" } ], "heading": { "level": 2, "text": "ReplayStarted" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A request to stop the replay buffer has been issued.", "api": "events", "name": "ReplayStopping", "category": "replay buffer", "since": "4.2.0", "names": [ { "name": "", "description": "ReplayStopping" } ], "categories": [ { "name": "", "description": "replay buffer" } ], "sinces": [ { "name": "", "description": "4.2.0" } ], "heading": { "level": 2, "text": "ReplayStopping" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Replay Buffer stopped successfully", "api": "events", "name": "ReplayStopped", "category": "replay buffer", "since": "4.2.0", "names": [ { "name": "", "description": "ReplayStopped" } ], "categories": [ { "name": "", "description": "replay buffer" } ], "sinces": [ { "name": "", "description": "4.2.0" } ], "heading": { "level": 2, "text": "ReplayStopped" }, "lead": "", "type": "class", "examples": [] } ], "other": [ { "subheads": [], "description": "OBS is exiting.", "api": "events", "name": "Exiting", "category": "other", "since": "0.3", "names": [ { "name": "", "description": "Exiting" } ], "categories": [ { "name": "", "description": "other" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "Exiting" }, "lead": "", "type": "class", "examples": [] } ], "general": [ { "subheads": [], "description": "Emitted every 2 seconds after enabling it by calling SetHeartbeat.", "return": [ "{boolean} `pulse` Toggles between every JSON message as an \"I am alive\" indicator.", "{string (optional)} `current-profile` Current active profile.", "{string (optional)} `current-scene` Current active scene.", "{boolean (optional)} `streaming` Current streaming state.", "{int (optional)} `total-stream-time` Total time (in seconds) since the stream started.", "{int (optional)} `total-stream-bytes` Total bytes sent since the stream started.", "{int (optional)} `total-stream-frames` Total frames streamed since the stream started.", "{boolean (optional)} `recording` Current recording state.", "{int (optional)} `total-record-time` Total time (in seconds) since recording started.", "{int (optional)} `total-record-bytes` Total bytes recorded since the recording started.", "{int (optional)} `total-record-frames` Total frames recorded since the recording started.", "{OBSStats} `stats` OBS Stats" ], "api": "events", "name": "Heartbeat", "category": "general", "since": "v0.3", "returns": [ { "type": "boolean", "name": "pulse", "description": "Toggles between every JSON message as an \"I am alive\" indicator." }, { "type": "string (optional)", "name": "current-profile", "description": "Current active profile." }, { "type": "string (optional)", "name": "current-scene", "description": "Current active scene." }, { "type": "boolean (optional)", "name": "streaming", "description": "Current streaming state." }, { "type": "int (optional)", "name": "total-stream-time", "description": "Total time (in seconds) since the stream started." }, { "type": "int (optional)", "name": "total-stream-bytes", "description": "Total bytes sent since the stream started." }, { "type": "int (optional)", "name": "total-stream-frames", "description": "Total frames streamed since the stream started." }, { "type": "boolean (optional)", "name": "recording", "description": "Current recording state." }, { "type": "int (optional)", "name": "total-record-time", "description": "Total time (in seconds) since recording started." }, { "type": "int (optional)", "name": "total-record-bytes", "description": "Total bytes recorded since the recording started." }, { "type": "int (optional)", "name": "total-record-frames", "description": "Total frames recorded since the recording started." }, { "type": "OBSStats", "name": "stats", "description": "OBS Stats" } ], "names": [ { "name": "", "description": "Heartbeat" } ], "categories": [ { "name": "", "description": "general" } ], "sinces": [ { "name": "", "description": "v0.3" } ], "heading": { "level": 2, "text": "Heartbeat" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A custom broadcast message, sent by the server, requested by one of the websocket clients.", "return": [ "{String} `realm` Identifier provided by the sender", "{Object} `data` User-defined data" ], "api": "events", "name": "BroadcastCustomMessage", "category": "general", "since": "4.7.0", "returns": [ { "type": "String", "name": "realm", "description": "Identifier provided by the sender" }, { "type": "Object", "name": "data", "description": "User-defined data" } ], "names": [ { "name": "", "description": "BroadcastCustomMessage" } ], "categories": [ { "name": "", "description": "general" } ], "sinces": [ { "name": "", "description": "4.7.0" } ], "heading": { "level": 2, "text": "BroadcastCustomMessage" }, "lead": "", "type": "class", "examples": [] } ], "sources": [ { "subheads": [], "description": "A source has been created. A source can be an input, a scene or a transition.", "return": [ "{String} `sourceName` Source name", "{String} `sourceType` Source type. Can be \"input\", \"scene\", \"transition\" or \"filter\".", "{String} `sourceKind` Source kind.", "{Object} `sourceSettings` Source settings" ], "api": "events", "name": "SourceCreated", "category": "sources", "since": "4.6.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "sourceType", "description": "Source type. Can be \"input\", \"scene\", \"transition\" or \"filter\"." }, { "type": "String", "name": "sourceKind", "description": "Source kind." }, { "type": "Object", "name": "sourceSettings", "description": "Source settings" } ], "names": [ { "name": "", "description": "SourceCreated" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "SourceCreated" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A source has been destroyed/removed. A source can be an input, a scene or a transition.", "return": [ "{String} `sourceName` Source name", "{String} `sourceType` Source type. Can be \"input\", \"scene\", \"transition\" or \"filter\".", "{String} `sourceKind` Source kind." ], "api": "events", "name": "SourceDestroyed", "category": "sources", "since": "4.6.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "sourceType", "description": "Source type. Can be \"input\", \"scene\", \"transition\" or \"filter\"." }, { "type": "String", "name": "sourceKind", "description": "Source kind." } ], "names": [ { "name": "", "description": "SourceDestroyed" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "SourceDestroyed" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "The volume of a source has changed.", "return": [ "{String} `sourceName` Source name", "{float} `volume` Source volume" ], "api": "events", "name": "SourceVolumeChanged", "category": "sources", "since": "4.6.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "float", "name": "volume", "description": "Source volume" } ], "names": [ { "name": "", "description": "SourceVolumeChanged" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "SourceVolumeChanged" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A source has been muted or unmuted.", "return": [ "{String} `sourceName` Source name", "{boolean} `muted` Mute status of the source" ], "api": "events", "name": "SourceMuteStateChanged", "category": "sources", "since": "4.6.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "boolean", "name": "muted", "description": "Mute status of the source" } ], "names": [ { "name": "", "description": "SourceMuteStateChanged" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "SourceMuteStateChanged" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A source has removed audio.", "return": "{String} `sourceName` Source name", "api": "events", "name": "SourceAudioDeactivated", "category": "sources", "since": "4.9.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" } ], "names": [ { "name": "", "description": "SourceAudioDeactivated" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "SourceAudioDeactivated" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A source has added audio.", "return": "{String} `sourceName` Source name", "api": "events", "name": "SourceAudioActivated", "category": "sources", "since": "4.9.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" } ], "names": [ { "name": "", "description": "SourceAudioActivated" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "SourceAudioActivated" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "The audio sync offset of a source has changed.", "return": [ "{String} `sourceName` Source name", "{int} `syncOffset` Audio sync offset of the source (in nanoseconds)" ], "api": "events", "name": "SourceAudioSyncOffsetChanged", "category": "sources", "since": "4.6.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "int", "name": "syncOffset", "description": "Audio sync offset of the source (in nanoseconds)" } ], "names": [ { "name": "", "description": "SourceAudioSyncOffsetChanged" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "SourceAudioSyncOffsetChanged" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Audio mixer routing changed on a source.", "return": [ "{String} `sourceName` Source name", "{Array} `mixers` Routing status of the source for each audio mixer (array of 6 values)", "{int} `mixers.*.id` Mixer number", "{boolean} `mixers.*.enabled` Routing status", "{String} `hexMixersValue` Raw mixer flags (little-endian, one bit per mixer) as an hexadecimal value" ], "api": "events", "name": "SourceAudioMixersChanged", "category": "sources", "since": "4.6.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "Array", "name": "mixers", "description": "Routing status of the source for each audio mixer (array of 6 values)" }, { "type": "int", "name": "mixers.*.id", "description": "Mixer number" }, { "type": "boolean", "name": "mixers.*.enabled", "description": "Routing status" }, { "type": "String", "name": "hexMixersValue", "description": "Raw mixer flags (little-endian, one bit per mixer) as an hexadecimal value" } ], "names": [ { "name": "", "description": "SourceAudioMixersChanged" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "SourceAudioMixersChanged" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A source has been renamed.", "return": [ "{String} `previousName` Previous source name", "{String} `newName` New source name", "{String} `sourceType` Type of source (input, scene, filter, transition)" ], "api": "events", "name": "SourceRenamed", "category": "sources", "since": "4.6.0", "returns": [ { "type": "String", "name": "previousName", "description": "Previous source name" }, { "type": "String", "name": "newName", "description": "New source name" }, { "type": "String", "name": "sourceType", "description": "Type of source (input, scene, filter, transition)" } ], "names": [ { "name": "", "description": "SourceRenamed" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "SourceRenamed" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A filter was added to a source.", "return": [ "{String} `sourceName` Source name", "{String} `filterName` Filter name", "{String} `filterType` Filter type", "{Object} `filterSettings` Filter settings" ], "api": "events", "name": "SourceFilterAdded", "category": "sources", "since": "4.6.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "filterName", "description": "Filter name" }, { "type": "String", "name": "filterType", "description": "Filter type" }, { "type": "Object", "name": "filterSettings", "description": "Filter settings" } ], "names": [ { "name": "", "description": "SourceFilterAdded" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "SourceFilterAdded" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A filter was removed from a source.", "return": [ "{String} `sourceName` Source name", "{String} `filterName` Filter name", "{String} `filterType` Filter type" ], "api": "events", "name": "SourceFilterRemoved", "category": "sources", "since": "4.6.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "filterName", "description": "Filter name" }, { "type": "String", "name": "filterType", "description": "Filter type" } ], "names": [ { "name": "", "description": "SourceFilterRemoved" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "SourceFilterRemoved" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "The visibility/enabled state of a filter changed", "return": [ "{String} `sourceName` Source name", "{String} `filterName` Filter name", "{Boolean} `filterEnabled` New filter state" ], "api": "events", "name": "SourceFilterVisibilityChanged", "category": "sources", "since": "4.7.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "filterName", "description": "Filter name" }, { "type": "Boolean", "name": "filterEnabled", "description": "New filter state" } ], "names": [ { "name": "", "description": "SourceFilterVisibilityChanged" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.7.0" } ], "heading": { "level": 2, "text": "SourceFilterVisibilityChanged" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Filters in a source have been reordered.", "return": [ "{String} `sourceName` Source name", "{Array} `filters` Ordered Filters list", "{String} `filters.*.name` Filter name", "{String} `filters.*.type` Filter type", "{boolean} `filters.*.enabled` Filter visibility status" ], "api": "events", "name": "SourceFiltersReordered", "category": "sources", "since": "4.6.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "Array", "name": "filters", "description": "Ordered Filters list" }, { "type": "String", "name": "filters.*.name", "description": "Filter name" }, { "type": "String", "name": "filters.*.type", "description": "Filter type" }, { "type": "boolean", "name": "filters.*.enabled", "description": "Filter visibility status" } ], "names": [ { "name": "", "description": "SourceFiltersReordered" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "SourceFiltersReordered" }, "lead": "", "type": "class", "examples": [] } ], "media": [ { "subheads": [], "description": "\n\nNote: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally.", "return": [ "{String} `sourceName` Source name", "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" ], "api": "events", "name": "MediaPlaying", "category": "media", "since": "4.9.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "sourceKind", "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" } ], "names": [ { "name": "", "description": "MediaPlaying" } ], "categories": [ { "name": "", "description": "media" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "MediaPlaying" }, "lead": "A media source has started playing.", "type": "class", "examples": [] }, { "subheads": [], "description": "\n\nNote: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally.", "return": [ "{String} `sourceName` Source name", "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" ], "api": "events", "name": "MediaPaused", "category": "media", "since": "4.9.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "sourceKind", "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" } ], "names": [ { "name": "", "description": "MediaPaused" } ], "categories": [ { "name": "", "description": "media" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "MediaPaused" }, "lead": "A media source has been paused.", "type": "class", "examples": [] }, { "subheads": [], "description": "\n\nNote: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally.", "return": [ "{String} `sourceName` Source name", "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" ], "api": "events", "name": "MediaRestarted", "category": "media", "since": "4.9.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "sourceKind", "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" } ], "names": [ { "name": "", "description": "MediaRestarted" } ], "categories": [ { "name": "", "description": "media" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "MediaRestarted" }, "lead": "A media source has been restarted.", "type": "class", "examples": [] }, { "subheads": [], "description": "\n\nNote: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally.", "return": [ "{String} `sourceName` Source name", "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" ], "api": "events", "name": "MediaStopped", "category": "media", "since": "4.9.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "sourceKind", "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" } ], "names": [ { "name": "", "description": "MediaStopped" } ], "categories": [ { "name": "", "description": "media" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "MediaStopped" }, "lead": "A media source has been stopped.", "type": "class", "examples": [] }, { "subheads": [], "description": "\n\nNote: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally.", "return": [ "{String} `sourceName` Source name", "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" ], "api": "events", "name": "MediaNext", "category": "media", "since": "4.9.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "sourceKind", "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" } ], "names": [ { "name": "", "description": "MediaNext" } ], "categories": [ { "name": "", "description": "media" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "MediaNext" }, "lead": "A media source has gone to the next item in the playlist.", "type": "class", "examples": [] }, { "subheads": [], "description": "\n\nNote: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally.", "return": [ "{String} `sourceName` Source name", "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" ], "api": "events", "name": "MediaPrevious", "category": "media", "since": "4.9.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "sourceKind", "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" } ], "names": [ { "name": "", "description": "MediaPrevious" } ], "categories": [ { "name": "", "description": "media" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "MediaPrevious" }, "lead": "A media source has gone to the previous item in the playlist.", "type": "class", "examples": [] }, { "subheads": [], "description": "\n\nNote: These events are emitted by the OBS sources themselves. For example when the media file starts playing. The behavior depends on the type of media source being used.", "return": [ "{String} `sourceName` Source name", "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" ], "api": "events", "name": "MediaStarted", "category": "media", "since": "4.9.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "sourceKind", "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" } ], "names": [ { "name": "", "description": "MediaStarted" } ], "categories": [ { "name": "", "description": "media" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "MediaStarted" }, "lead": "A media source has been started.", "type": "class", "examples": [] }, { "subheads": [], "description": "\n\nNote: These events are emitted by the OBS sources themselves. For example when the media file ends. The behavior depends on the type of media source being used.", "return": [ "{String} `sourceName` Source name", "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" ], "api": "events", "name": "MediaEnded", "category": "media", "since": "4.9.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "sourceKind", "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" } ], "names": [ { "name": "", "description": "MediaEnded" } ], "categories": [ { "name": "", "description": "media" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "MediaEnded" }, "lead": "A media source has ended.", "type": "class", "examples": [] } ], "scene items": [ { "subheads": [], "description": "Scene items within a scene have been reordered.", "return": [ "{String} `scene-name` Name of the scene where items have been reordered.", "{Array} `scene-items` Ordered list of scene items", "{String} `scene-items.*.source-name` Item source name", "{int} `scene-items.*.item-id` Scene item unique ID" ], "api": "events", "name": "SourceOrderChanged", "category": "scene items", "since": "4.0.0", "returns": [ { "type": "String", "name": "scene-name", "description": "Name of the scene where items have been reordered." }, { "type": "Array", "name": "scene-items", "description": "Ordered list of scene items" }, { "type": "String", "name": "scene-items.*.source-name", "description": "Item source name" }, { "type": "int", "name": "scene-items.*.item-id", "description": "Scene item unique ID" } ], "names": [ { "name": "", "description": "SourceOrderChanged" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "SourceOrderChanged" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A scene item has been added to a scene.", "return": [ "{String} `scene-name` Name of the scene.", "{String} `item-name` Name of the item added to the scene.", "{int} `item-id` Scene item ID" ], "api": "events", "name": "SceneItemAdded", "category": "scene items", "since": "4.0.0", "returns": [ { "type": "String", "name": "scene-name", "description": "Name of the scene." }, { "type": "String", "name": "item-name", "description": "Name of the item added to the scene." }, { "type": "int", "name": "item-id", "description": "Scene item ID" } ], "names": [ { "name": "", "description": "SceneItemAdded" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "SceneItemAdded" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A scene item has been removed from a scene.", "return": [ "{String} `scene-name` Name of the scene.", "{String} `item-name` Name of the item removed from the scene.", "{int} `item-id` Scene item ID" ], "api": "events", "name": "SceneItemRemoved", "category": "scene items", "since": "4.0.0", "returns": [ { "type": "String", "name": "scene-name", "description": "Name of the scene." }, { "type": "String", "name": "item-name", "description": "Name of the item removed from the scene." }, { "type": "int", "name": "item-id", "description": "Scene item ID" } ], "names": [ { "name": "", "description": "SceneItemRemoved" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "SceneItemRemoved" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A scene item's visibility has been toggled.", "return": [ "{String} `scene-name` Name of the scene.", "{String} `item-name` Name of the item in the scene.", "{int} `item-id` Scene item ID", "{boolean} `item-visible` New visibility state of the item." ], "api": "events", "name": "SceneItemVisibilityChanged", "category": "scene items", "since": "4.0.0", "returns": [ { "type": "String", "name": "scene-name", "description": "Name of the scene." }, { "type": "String", "name": "item-name", "description": "Name of the item in the scene." }, { "type": "int", "name": "item-id", "description": "Scene item ID" }, { "type": "boolean", "name": "item-visible", "description": "New visibility state of the item." } ], "names": [ { "name": "", "description": "SceneItemVisibilityChanged" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "SceneItemVisibilityChanged" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A scene item's locked status has been toggled.", "return": [ "{String} `scene-name` Name of the scene.", "{String} `item-name` Name of the item in the scene.", "{int} `item-id` Scene item ID", "{boolean} `item-locked` New locked state of the item." ], "api": "events", "name": "SceneItemLockChanged", "category": "scene items", "since": "4.8.0", "returns": [ { "type": "String", "name": "scene-name", "description": "Name of the scene." }, { "type": "String", "name": "item-name", "description": "Name of the item in the scene." }, { "type": "int", "name": "item-id", "description": "Scene item ID" }, { "type": "boolean", "name": "item-locked", "description": "New locked state of the item." } ], "names": [ { "name": "", "description": "SceneItemLockChanged" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.8.0" } ], "heading": { "level": 2, "text": "SceneItemLockChanged" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A scene item's transform has been changed.", "return": [ "{String} `scene-name` Name of the scene.", "{String} `item-name` Name of the item in the scene.", "{int} `item-id` Scene item ID", "{SceneItemTransform} `transform` Scene item transform properties" ], "api": "events", "name": "SceneItemTransformChanged", "category": "scene items", "since": "4.6.0", "returns": [ { "type": "String", "name": "scene-name", "description": "Name of the scene." }, { "type": "String", "name": "item-name", "description": "Name of the item in the scene." }, { "type": "int", "name": "item-id", "description": "Scene item ID" }, { "type": "SceneItemTransform", "name": "transform", "description": "Scene item transform properties" } ], "names": [ { "name": "", "description": "SceneItemTransformChanged" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "SceneItemTransformChanged" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A scene item is selected.", "return": [ "{String} `scene-name` Name of the scene.", "{String} `item-name` Name of the item in the scene.", "{int} `item-id` Name of the item in the scene." ], "api": "events", "name": "SceneItemSelected", "category": "scene items", "since": "4.6.0", "returns": [ { "type": "String", "name": "scene-name", "description": "Name of the scene." }, { "type": "String", "name": "item-name", "description": "Name of the item in the scene." }, { "type": "int", "name": "item-id", "description": "Name of the item in the scene." } ], "names": [ { "name": "", "description": "SceneItemSelected" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "SceneItemSelected" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "A scene item is deselected.", "return": [ "{String} `scene-name` Name of the scene.", "{String} `item-name` Name of the item in the scene.", "{int} `item-id` Name of the item in the scene." ], "api": "events", "name": "SceneItemDeselected", "category": "scene items", "since": "4.6.0", "returns": [ { "type": "String", "name": "scene-name", "description": "Name of the scene." }, { "type": "String", "name": "item-name", "description": "Name of the item in the scene." }, { "type": "int", "name": "item-id", "description": "Name of the item in the scene." } ], "names": [ { "name": "", "description": "SceneItemDeselected" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "SceneItemDeselected" }, "lead": "", "type": "class", "examples": [] } ], "studio mode": [ { "subheads": [], "description": "The selected preview scene has changed (only available in Studio Mode).", "return": [ "{String} `scene-name` Name of the scene being previewed.", "{Array} `sources` List of sources composing the scene. Same specification as [`GetCurrentScene`](#getcurrentscene)." ], "api": "events", "name": "PreviewSceneChanged", "category": "studio mode", "since": "4.1.0", "returns": [ { "type": "String", "name": "scene-name", "description": "Name of the scene being previewed." }, { "type": "Array", "name": "sources", "description": "List of sources composing the scene. Same specification as [`GetCurrentScene`](#getcurrentscene)." } ], "names": [ { "name": "", "description": "PreviewSceneChanged" } ], "categories": [ { "name": "", "description": "studio mode" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "PreviewSceneChanged" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "returns": [ { "type": "boolean", "name": "new-state", "description": "The new enabled state of Studio Mode." } ], "names": [ { "name": "", "description": "StudioModeSwitched" } ], "categories": [ { "name": "", "description": "studio mode" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "StudioModeSwitched" }, "lead": "", "type": "class", "examples": [] } ] }, "requests": { "general": [ { "subheads": [], "description": "Returns the latest version of the plugin and the API.", "return": [ "{double} `version` OBSRemote compatible API version. Fixed to 1.1 for retrocompatibility.", "{String} `obs-websocket-version` obs-websocket plugin version.", "{String} `obs-studio-version` OBS Studio program version.", "{String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : \"Method1,Method2,Method3\").", "{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", "returns": [ { "type": "double", "name": "version", "description": "OBSRemote compatible API version. Fixed to 1.1 for retrocompatibility." }, { "type": "String", "name": "obs-websocket-version", "description": "obs-websocket plugin version." }, { "type": "String", "name": "obs-studio-version", "description": "OBS Studio program version." }, { "type": "String", "name": "available-requests", "description": "List of available request types, formatted as a comma-separated list string (e.g. : \"Method1,Method2,Method3\")." }, { "type": "String", "name": "supported-image-export-formats", "description": "List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string" } ], "names": [ { "name": "", "description": "GetVersion" } ], "categories": [ { "name": "", "description": "general" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "GetVersion" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Tells the client if authentication is required. If so, returns authentication parameters `challenge`\nand `salt` (see \"Authentication\" for more information).", "return": [ "{boolean} `authRequired` Indicates whether authentication is required.", "{String (optional)} `challenge`", "{String (optional)} `salt`" ], "api": "requests", "name": "GetAuthRequired", "category": "general", "since": "0.3", "returns": [ { "type": "boolean", "name": "authRequired", "description": "Indicates whether authentication is required." }, { "type": "String (optional)", "name": "challenge", "description": "" }, { "type": "String (optional)", "name": "salt", "description": "" } ], "names": [ { "name": "", "description": "GetAuthRequired" } ], "categories": [ { "name": "", "description": "general" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "GetAuthRequired" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "params": [ { "type": "String", "name": "auth", "description": "Response to the auth challenge (see \"Authentication\" for more information)." } ], "names": [ { "name": "", "description": "Authenticate" } ], "categories": [ { "name": "", "description": "general" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "Authenticate" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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.", "params": [ { "type": "boolean", "name": "enable", "description": "Starts/Stops emitting heartbeat messages" } ], "names": [ { "name": "", "description": "SetHeartbeat" } ], "categories": [ { "name": "", "description": "general" } ], "sinces": [ { "name": "", "description": "4.3.0" } ], "deprecateds": [ { "name": "", "description": "Since 4.9.0. Please poll the appropriate data using requests. Will be removed in v5.0.0." } ], "heading": { "level": 2, "text": "SetHeartbeat" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Set the filename formatting string", "param": "{String} `filename-formatting` Filename formatting string to set.", "api": "requests", "name": "SetFilenameFormatting", "category": "general", "since": "4.3.0", "params": [ { "type": "String", "name": "filename-formatting", "description": "Filename formatting string to set." } ], "names": [ { "name": "", "description": "SetFilenameFormatting" } ], "categories": [ { "name": "", "description": "general" } ], "sinces": [ { "name": "", "description": "4.3.0" } ], "heading": { "level": 2, "text": "SetFilenameFormatting" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the filename formatting string", "return": "{String} `filename-formatting` Current filename formatting string.", "api": "requests", "name": "GetFilenameFormatting", "category": "general", "since": "4.3.0", "returns": [ { "type": "String", "name": "filename-formatting", "description": "Current filename formatting string." } ], "names": [ { "name": "", "description": "GetFilenameFormatting" } ], "categories": [ { "name": "", "description": "general" } ], "sinces": [ { "name": "", "description": "4.3.0" } ], "heading": { "level": 2, "text": "GetFilenameFormatting" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "returns": [ { "type": "OBSStats", "name": "stats", "description": "[OBS stats](#obsstats)" } ], "names": [ { "name": "", "description": "GetStats" } ], "categories": [ { "name": "", "description": "general" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "GetStats" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Broadcast custom message to all connected WebSocket clients", "param": [ "{String} `realm` Identifier to be choosen by the client", "{Object} `data` User-defined data" ], "api": "requests", "name": "BroadcastCustomMessage", "category": "general", "since": "4.7.0", "params": [ { "type": "String", "name": "realm", "description": "Identifier to be choosen by the client" }, { "type": "Object", "name": "data", "description": "User-defined data" } ], "names": [ { "name": "", "description": "BroadcastCustomMessage" } ], "categories": [ { "name": "", "description": "general" } ], "sinces": [ { "name": "", "description": "4.7.0" } ], "heading": { "level": 2, "text": "BroadcastCustomMessage" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get basic OBS video information", "return": [ "{int} `baseWidth` Base (canvas) width", "{int} `baseHeight` Base (canvas) height", "{int} `outputWidth` Output width", "{int} `outputHeight` Output height", "{String} `scaleType` Scaling method used if output size differs from base size", "{double} `fps` Frames rendered per second", "{String} `videoFormat` Video color format", "{String} `colorSpace` Color space for YUV", "{String} `colorRange` Color range (full or partial)" ], "api": "requests", "name": "GetVideoInfo", "category": "general", "since": "4.6.0", "returns": [ { "type": "int", "name": "baseWidth", "description": "Base (canvas) width" }, { "type": "int", "name": "baseHeight", "description": "Base (canvas) height" }, { "type": "int", "name": "outputWidth", "description": "Output width" }, { "type": "int", "name": "outputHeight", "description": "Output height" }, { "type": "String", "name": "scaleType", "description": "Scaling method used if output size differs from base size" }, { "type": "double", "name": "fps", "description": "Frames rendered per second" }, { "type": "String", "name": "videoFormat", "description": "Video color format" }, { "type": "String", "name": "colorSpace", "description": "Color space for YUV" }, { "type": "String", "name": "colorRange", "description": "Color range (full or partial)" } ], "names": [ { "name": "", "description": "GetVideoInfo" } ], "categories": [ { "name": "", "description": "general" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "GetVideoInfo" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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).", "{int (Optional)} `monitor` Monitor to open the projector on. If -1 or omitted, opens a window.", "{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.", "{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", "params": [ { "type": "String (Optional)", "name": "type", "description": "Type of projector: `Preview` (default), `Source`, `Scene`, `StudioProgram`, or `Multiview` (case insensitive)." }, { "type": "int (Optional)", "name": "monitor", "description": "Monitor to open the projector on. If -1 or omitted, opens a window." }, { "type": "String (Optional)", "name": "geometry", "description": "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." }, { "type": "String (Optional)", "name": "name", "description": "Name of the source or scene to be displayed (ignored for other projector types)." } ], "names": [ { "name": "", "description": "OpenProjector" } ], "categories": [ { "name": "", "description": "general" } ], "sinces": [ { "name": "", "description": "4.8.0" } ], "heading": { "level": 2, "text": "OpenProjector" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "params": [ { "type": "String", "name": "hotkeyName", "description": "Unique name of the hotkey, as defined when registering the hotkey (e.g. \"ReplayBuffer.Save\")" } ], "names": [ { "name": "", "description": "TriggerHotkeyByName" } ], "categories": [ { "name": "", "description": "general" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "TriggerHotkeyByName" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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)", "{Object (Optional)} `keyModifiers` Optional key modifiers object. False entries can be ommitted", "{boolean} `keyModifiers.shift` Trigger Shift Key", "{boolean} `keyModifiers.alt` Trigger Alt Key", "{boolean} `keyModifiers.control` Trigger Control (Ctrl) Key", "{boolean} `keyModifiers.command` Trigger Command Key (Mac)" ], "api": "requests", "name": "TriggerHotkeyBySequence", "category": "general", "since": "4.9.0", "params": [ { "type": "String", "name": "keyId", "description": "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)" }, { "type": "Object (Optional)", "name": "keyModifiers", "description": "Optional key modifiers object. False entries can be ommitted" }, { "type": "boolean", "name": "keyModifiers.shift", "description": "Trigger Shift Key" }, { "type": "boolean", "name": "keyModifiers.alt", "description": "Trigger Alt Key" }, { "type": "boolean", "name": "keyModifiers.control", "description": "Trigger Control (Ctrl) Key" }, { "type": "boolean", "name": "keyModifiers.command", "description": "Trigger Command Key (Mac)" } ], "names": [ { "name": "", "description": "TriggerHotkeyBySequence" } ], "categories": [ { "name": "", "description": "general" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "TriggerHotkeyBySequence" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Executes a list of requests sequentially (one-by-one on the same thread).", "param": [ "{Array} `requests` Array of requests to perform. Executed in order.", "{String} `requests.*.request-type` Request type. Eg. `GetVersion`.", "{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.", "{boolean (Optional)} `abortOnFail` Stop processing batch requests if one returns a failure." ], "return": [ "{Array} `results` Batch requests results, ordered sequentially.", "{String} `results.*.message-id` ID of the individual request which was originally provided by the client.", "{String} `results.*.status` Status response as string. Either `ok` or `error`.", "{String (Optional)} `results.*.error` Error message accompanying an `error` status." ], "api": "requests", "name": "ExecuteBatch", "category": "general", "since": "4.9.0", "returns": [ { "type": "Array", "name": "results", "description": "Batch requests results, ordered sequentially." }, { "type": "String", "name": "results.*.message-id", "description": "ID of the individual request which was originally provided by the client." }, { "type": "String", "name": "results.*.status", "description": "Status response as string. Either `ok` or `error`." }, { "type": "String (Optional)", "name": "results.*.error", "description": "Error message accompanying an `error` status." } ], "params": [ { "type": "Array", "name": "requests", "description": "Array of requests to perform. Executed in order." }, { "type": "String", "name": "requests.*.request-type", "description": "Request type. Eg. `GetVersion`." }, { "type": "String (Optional)", "name": "requests.*.message-id", "description": "ID of the individual request. Can be any string and not required to be unique. Defaults to empty string if not specified." }, { "type": "boolean (Optional)", "name": "abortOnFail", "description": "Stop processing batch requests if one returns a failure." } ], "names": [ { "name": "", "description": "ExecuteBatch" } ], "categories": [ { "name": "", "description": "general" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "ExecuteBatch" }, "lead": "", "type": "class", "examples": [] } ], "media control": [ { "subheads": [], "description": "Pause or play a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)", "param": [ "{String} `sourceName` Source name.", "{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", "params": [ { "type": "String", "name": "sourceName", "description": "Source name." }, { "type": "boolean", "name": "playPause", "description": "Whether to pause or play the source. `false` for play, `true` for pause." } ], "names": [ { "name": "", "description": "PlayPauseMedia" } ], "categories": [ { "name": "", "description": "media control" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "PlayPauseMedia" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "params": [ { "type": "String", "name": "sourceName", "description": "Source name." } ], "names": [ { "name": "", "description": "RestartMedia" } ], "categories": [ { "name": "", "description": "media control" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "RestartMedia" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "params": [ { "type": "String", "name": "sourceName", "description": "Source name." } ], "names": [ { "name": "", "description": "StopMedia" } ], "categories": [ { "name": "", "description": "media control" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "StopMedia" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "params": [ { "type": "String", "name": "sourceName", "description": "Source name." } ], "names": [ { "name": "", "description": "NextMedia" } ], "categories": [ { "name": "", "description": "media control" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "NextMedia" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "params": [ { "type": "String", "name": "sourceName", "description": "Source name." } ], "names": [ { "name": "", "description": "PreviousMedia" } ], "categories": [ { "name": "", "description": "media control" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "PreviousMedia" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the length of media in milliseconds. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)\nNote: 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", "returns": [ { "type": "int", "name": "mediaDuration", "description": "The total length of media in milliseconds.." } ], "params": [ { "type": "String", "name": "sourceName", "description": "Source name." } ], "names": [ { "name": "", "description": "GetMediaDuration" } ], "categories": [ { "name": "", "description": "media control" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "GetMediaDuration" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "returns": [ { "type": "int", "name": "timestamp", "description": "The time in milliseconds since the start of the media." } ], "params": [ { "type": "String", "name": "sourceName", "description": "Source name." } ], "names": [ { "name": "", "description": "GetMediaTime" } ], "categories": [ { "name": "", "description": "media control" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "GetMediaTime" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Set the timestamp of a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)", "param": [ "{String} `sourceName` Source name.", "{int} `timestamp` Milliseconds to set the timestamp to." ], "api": "requests", "name": "SetMediaTime", "category": "media control", "since": "4.9.0", "params": [ { "type": "String", "name": "sourceName", "description": "Source name." }, { "type": "int", "name": "timestamp", "description": "Milliseconds to set the timestamp to." } ], "names": [ { "name": "", "description": "SetMediaTime" } ], "categories": [ { "name": "", "description": "media control" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "SetMediaTime" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Scrub media using a supplied offset. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)\nNote: 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.", "{int} `timeOffset` Millisecond offset (positive or negative) to offset the current media position." ], "api": "requests", "name": "ScrubMedia", "category": "media control", "since": "4.9.0", "params": [ { "type": "String", "name": "sourceName", "description": "Source name." }, { "type": "int", "name": "timeOffset", "description": "Millisecond offset (positive or negative) to offset the current media position." } ], "names": [ { "name": "", "description": "ScrubMedia" } ], "categories": [ { "name": "", "description": "media control" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "ScrubMedia" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "returns": [ { "type": "String", "name": "mediaState", "description": "The media state of the provided source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown`" } ], "params": [ { "type": "String", "name": "sourceName", "description": "Source name." } ], "names": [ { "name": "", "description": "GetMediaState" } ], "categories": [ { "name": "", "description": "media control" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "GetMediaState" }, "lead": "", "type": "class", "examples": [] } ], "sources": [ { "subheads": [], "description": "List the media state of all media sources (vlc and media source)", "return": [ "{Array} `mediaSources` Array of sources", "{String} `mediaSources.*.sourceName` Unique source name", "{String} `mediaSources.*.sourceKind` Unique source internal type (a.k.a `ffmpeg_source` or `vlc_source`)", "{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", "returns": [ { "type": "Array", "name": "mediaSources", "description": "Array of sources" }, { "type": "String", "name": "mediaSources.*.sourceName", "description": "Unique source name" }, { "type": "String", "name": "mediaSources.*.sourceKind", "description": "Unique source internal type (a.k.a `ffmpeg_source` or `vlc_source`)" }, { "type": "String", "name": "mediaSources.*.mediaState", "description": "The current state of media for that source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown`" } ], "names": [ { "name": "", "description": "GetMediaSourcesList" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "GetMediaSourcesList" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Create a source and add it as a sceneitem to a scene.", "param": [ "{String} `sourceName` Source name.", "{String} `sourceKind` Source kind, Eg. `vlc_source`.", "{String} `sceneName` Scene to add the new source to.", "{Object (optional)} `sourceSettings` Source settings data.", "{boolean (optional)} `setVisible` Set the created SceneItem as visible or not. Defaults to true" ], "return": "{int} `itemId` ID of the SceneItem in the scene.", "api": "requests", "name": "CreateSource", "category": "sources", "since": "4.9.0", "returns": [ { "type": "int", "name": "itemId", "description": "ID of the SceneItem in the scene." } ], "params": [ { "type": "String", "name": "sourceName", "description": "Source name." }, { "type": "String", "name": "sourceKind", "description": "Source kind, Eg. `vlc_source`." }, { "type": "String", "name": "sceneName", "description": "Scene to add the new source to." }, { "type": "Object (optional)", "name": "sourceSettings", "description": "Source settings data." }, { "type": "boolean (optional)", "name": "setVisible", "description": "Set the created SceneItem as visible or not. Defaults to true" } ], "names": [ { "name": "", "description": "CreateSource" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "CreateSource" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "List all sources available in the running OBS instance", "return": [ "{Array} `sources` Array of sources", "{String} `sources.*.name` Unique source name", "{String} `sources.*.typeId` Non-unique source internal type (a.k.a kind)", "{String} `sources.*.type` Source type. Value is one of the following: \"input\", \"filter\", \"transition\", \"scene\" or \"unknown\"" ], "api": "requests", "name": "GetSourcesList", "category": "sources", "since": "4.3.0", "returns": [ { "type": "Array", "name": "sources", "description": "Array of sources" }, { "type": "String", "name": "sources.*.name", "description": "Unique source name" }, { "type": "String", "name": "sources.*.typeId", "description": "Non-unique source internal type (a.k.a kind)" }, { "type": "String", "name": "sources.*.type", "description": "Source type. Value is one of the following: \"input\", \"filter\", \"transition\", \"scene\" or \"unknown\"" } ], "names": [ { "name": "", "description": "GetSourcesList" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.3.0" } ], "heading": { "level": 2, "text": "GetSourcesList" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get a list of all available sources types", "return": [ "{Array} `types` Array of source types", "{String} `types.*.typeId` Non-unique internal source type ID", "{String} `types.*.displayName` Display name of the source type", "{String} `types.*.type` Type. Value is one of the following: \"input\", \"filter\", \"transition\" or \"other\"", "{Object} `types.*.defaultSettings` Default settings of this source type", "{Object} `types.*.caps` Source type capabilities", "{Boolean} `types.*.caps.isAsync` True if source of this type provide frames asynchronously", "{Boolean} `types.*.caps.hasVideo` True if sources of this type provide video", "{Boolean} `types.*.caps.hasAudio` True if sources of this type provide audio", "{Boolean} `types.*.caps.canInteract` True if interaction with this sources of this type is possible", "{Boolean} `types.*.caps.isComposite` True if sources of this type composite one or more sub-sources", "{Boolean} `types.*.caps.doNotDuplicate` True if sources of this type should not be fully duplicated", "{Boolean} `types.*.caps.doNotSelfMonitor` True if sources of this type may cause a feedback loop if it's audio is monitored and shouldn't be" ], "api": "requests", "name": "GetSourceTypesList", "category": "sources", "since": "4.3.0", "returns": [ { "type": "Array", "name": "types", "description": "Array of source types" }, { "type": "String", "name": "types.*.typeId", "description": "Non-unique internal source type ID" }, { "type": "String", "name": "types.*.displayName", "description": "Display name of the source type" }, { "type": "String", "name": "types.*.type", "description": "Type. Value is one of the following: \"input\", \"filter\", \"transition\" or \"other\"" }, { "type": "Object", "name": "types.*.defaultSettings", "description": "Default settings of this source type" }, { "type": "Object", "name": "types.*.caps", "description": "Source type capabilities" }, { "type": "Boolean", "name": "types.*.caps.isAsync", "description": "True if source of this type provide frames asynchronously" }, { "type": "Boolean", "name": "types.*.caps.hasVideo", "description": "True if sources of this type provide video" }, { "type": "Boolean", "name": "types.*.caps.hasAudio", "description": "True if sources of this type provide audio" }, { "type": "Boolean", "name": "types.*.caps.canInteract", "description": "True if interaction with this sources of this type is possible" }, { "type": "Boolean", "name": "types.*.caps.isComposite", "description": "True if sources of this type composite one or more sub-sources" }, { "type": "Boolean", "name": "types.*.caps.doNotDuplicate", "description": "True if sources of this type should not be fully duplicated" }, { "type": "Boolean", "name": "types.*.caps.doNotSelfMonitor", "description": "True if sources of this type may cause a feedback loop if it's audio is monitored and shouldn't be" } ], "names": [ { "name": "", "description": "GetSourceTypesList" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.3.0" } ], "heading": { "level": 2, "text": "GetSourceTypesList" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the volume of the specified source. Default response uses mul format, NOT SLIDER PERCENTAGE.", "param": [ "{String} `source` Source name.", "{boolean (optional)} `useDecibel` Output volume in decibels of attenuation instead of amplitude/mul." ], "return": [ "{String} `name` Source name.", "{double} `volume` Volume of the source. Between `0.0` and `20.0` if using mul, under `26.0` if using dB.", "{boolean} `muted` Indicates whether the source is muted." ], "api": "requests", "name": "GetVolume", "category": "sources", "since": "4.0.0", "returns": [ { "type": "String", "name": "name", "description": "Source name." }, { "type": "double", "name": "volume", "description": "Volume of the source. Between `0.0` and `20.0` if using mul, under `26.0` if using dB." }, { "type": "boolean", "name": "muted", "description": "Indicates whether the source is muted." } ], "params": [ { "type": "String", "name": "source", "description": "Source name." }, { "type": "boolean (optional)", "name": "useDecibel", "description": "Output volume in decibels of attenuation instead of amplitude/mul." } ], "names": [ { "name": "", "description": "GetVolume" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "GetVolume" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Set the volume of the specified source. Default request format uses mul, NOT SLIDER PERCENTAGE.", "param": [ "{String} `source` Source name.", "{double} `volume` Desired volume. Must be between `0.0` and `20.0` for mul, and under 26.0 for dB. OBS will interpret dB values under -100.0 as Inf. Note: The OBS volume sliders only reach a maximum of 1.0mul/0.0dB, however OBS actually supports larger values.", "{boolean (optional)} `useDecibel` Interperet `volume` data as decibels instead of amplitude/mul." ], "api": "requests", "name": "SetVolume", "category": "sources", "since": "4.0.0", "params": [ { "type": "String", "name": "source", "description": "Source name." }, { "type": "double", "name": "volume", "description": "Desired volume. Must be between `0.0` and `20.0` for mul, and under 26.0 for dB. OBS will interpret dB values under -100.0 as Inf. Note: The OBS volume sliders only reach a maximum of 1.0mul/0.0dB, however OBS actually supports larger values." }, { "type": "boolean (optional)", "name": "useDecibel", "description": "Interperet `volume` data as decibels instead of amplitude/mul." } ], "names": [ { "name": "", "description": "SetVolume" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "SetVolume" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the mute status of a specified source.", "param": "{String} `source` Source name.", "return": [ "{String} `name` Source name.", "{boolean} `muted` Mute status of the source." ], "api": "requests", "name": "GetMute", "category": "sources", "since": "4.0.0", "returns": [ { "type": "String", "name": "name", "description": "Source name." }, { "type": "boolean", "name": "muted", "description": "Mute status of the source." } ], "params": [ { "type": "String", "name": "source", "description": "Source name." } ], "names": [ { "name": "", "description": "GetMute" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "GetMute" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Sets the mute status of a specified source.", "param": [ "{String} `source` Source name.", "{boolean} `mute` Desired mute status." ], "api": "requests", "name": "SetMute", "category": "sources", "since": "4.0.0", "params": [ { "type": "String", "name": "source", "description": "Source name." }, { "type": "boolean", "name": "mute", "description": "Desired mute status." } ], "names": [ { "name": "", "description": "SetMute" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "SetMute" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Inverts the mute status of a specified source.", "param": "{String} `source` Source name.", "api": "requests", "name": "ToggleMute", "category": "sources", "since": "4.0.0", "params": [ { "type": "String", "name": "source", "description": "Source name." } ], "names": [ { "name": "", "description": "ToggleMute" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "ToggleMute" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the audio's active status of a specified source.", "param": "{String} `sourceName` Source name.", "return": "{boolean} `audioActive` Audio active status of the source.", "api": "requests", "name": "GetAudioActive", "category": "sources", "since": "4.9.0", "returns": [ { "type": "boolean", "name": "audioActive", "description": "Audio active status of the source." } ], "params": [ { "type": "String", "name": "sourceName", "description": "Source name." } ], "names": [ { "name": "", "description": "GetAudioActive" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "GetAudioActive" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "\n\nNote: If the new name already exists as a source, obs-websocket will return an error.", "param": [ "{String} `sourceName` Source name.", "{String} `newName` New source name." ], "api": "requests", "name": "SetSourceName", "category": "sources", "since": "4.8.0", "params": [ { "type": "String", "name": "sourceName", "description": "Source name." }, { "type": "String", "name": "newName", "description": "New source name." } ], "names": [ { "name": "", "description": "SetSourceName" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.8.0" } ], "heading": { "level": 2, "text": "SetSourceName" }, "lead": "Sets (aka rename) the name of a source. Also works with scenes since scenes are technically sources in OBS.", "type": "class", "examples": [] }, { "subheads": [], "description": "Set the audio sync offset of a specified source.", "param": [ "{String} `source` Source name.", "{int} `offset` The desired audio sync offset (in nanoseconds)." ], "api": "requests", "name": "SetSyncOffset", "category": "sources", "since": "4.2.0", "params": [ { "type": "String", "name": "source", "description": "Source name." }, { "type": "int", "name": "offset", "description": "The desired audio sync offset (in nanoseconds)." } ], "names": [ { "name": "", "description": "SetSyncOffset" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.2.0" } ], "heading": { "level": 2, "text": "SetSyncOffset" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the audio sync offset of a specified source.", "param": "{String} `source` Source name.", "return": [ "{String} `name` Source name.", "{int} `offset` The audio sync offset (in nanoseconds)." ], "api": "requests", "name": "GetSyncOffset", "category": "sources", "since": "4.2.0", "returns": [ { "type": "String", "name": "name", "description": "Source name." }, { "type": "int", "name": "offset", "description": "The audio sync offset (in nanoseconds)." } ], "params": [ { "type": "String", "name": "source", "description": "Source name." } ], "names": [ { "name": "", "description": "GetSyncOffset" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.2.0" } ], "heading": { "level": 2, "text": "GetSyncOffset" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get settings of the specified source", "param": [ "{String} `sourceName` Source name.", "{String (optional)} `sourceType` Type of the specified source. Useful for type-checking if you expect a specific settings schema." ], "return": [ "{String} `sourceName` Source name", "{String} `sourceType` Type of the specified source", "{Object} `sourceSettings` Source settings (varies between source types, may require some probing around)." ], "api": "requests", "name": "GetSourceSettings", "category": "sources", "since": "4.3.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "sourceType", "description": "Type of the specified source" }, { "type": "Object", "name": "sourceSettings", "description": "Source settings (varies between source types, may require some probing around)." } ], "params": [ { "type": "String", "name": "sourceName", "description": "Source name." }, { "type": "String (optional)", "name": "sourceType", "description": "Type of the specified source. Useful for type-checking if you expect a specific settings schema." } ], "names": [ { "name": "", "description": "GetSourceSettings" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.3.0" } ], "heading": { "level": 2, "text": "GetSourceSettings" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Set settings of the specified source.", "param": [ "{String} `sourceName` Source name.", "{String (optional)} `sourceType` Type of the specified source. Useful for type-checking to avoid settings a set of settings incompatible with the actual source's type.", "{Object} `sourceSettings` Source settings (varies between source types, may require some probing around)." ], "return": [ "{String} `sourceName` Source name", "{String} `sourceType` Type of the specified source", "{Object} `sourceSettings` Updated source settings" ], "api": "requests", "name": "SetSourceSettings", "category": "sources", "since": "4.3.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "sourceType", "description": "Type of the specified source" }, { "type": "Object", "name": "sourceSettings", "description": "Updated source settings" } ], "params": [ { "type": "String", "name": "sourceName", "description": "Source name." }, { "type": "String (optional)", "name": "sourceType", "description": "Type of the specified source. Useful for type-checking to avoid settings a set of settings incompatible with the actual source's type." }, { "type": "Object", "name": "sourceSettings", "description": "Source settings (varies between source types, may require some probing around)." } ], "names": [ { "name": "", "description": "SetSourceSettings" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.3.0" } ], "heading": { "level": 2, "text": "SetSourceSettings" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the current properties of a Text GDI Plus source.", "param": "{String} `source` Source name.", "return": [ "{String} `source` Source name.", "{String} `align` Text Alignment (\"left\", \"center\", \"right\").", "{int} `bk_color` Background color.", "{int} `bk_opacity` Background opacity (0-100).", "{boolean} `chatlog` Chat log.", "{int} `chatlog_lines` Chat log lines.", "{int} `color` Text color.", "{boolean} `extents` Extents wrap.", "{int} `extents_cx` Extents cx.", "{int} `extents_cy` Extents cy.", "{String} `file` File path name.", "{boolean} `read_from_file` Read text from the specified file.", "{Object} `font` Holds data for the font. Ex: `\"font\": { \"face\": \"Arial\", \"flags\": 0, \"size\": 150, \"style\": \"\" }`", "{String} `font.face` Font face.", "{int} `font.flags` Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8`", "{int} `font.size` Font text size.", "{String} `font.style` Font Style (unknown function).", "{boolean} `gradient` Gradient enabled.", "{int} `gradient_color` Gradient color.", "{float} `gradient_dir` Gradient direction.", "{int} `gradient_opacity` Gradient opacity (0-100).", "{boolean} `outline` Outline.", "{int} `outline_color` Outline color.", "{int} `outline_size` Outline size.", "{int} `outline_opacity` Outline opacity (0-100).", "{String} `text` Text content to be displayed.", "{String} `valign` Text vertical alignment (\"top\", \"center\", \"bottom\").", "{boolean} `vertical` Vertical text enabled." ], "api": "requests", "name": "GetTextGDIPlusProperties", "category": "sources", "since": "4.1.0", "returns": [ { "type": "String", "name": "source", "description": "Source name." }, { "type": "String", "name": "align", "description": "Text Alignment (\"left\", \"center\", \"right\")." }, { "type": "int", "name": "bk_color", "description": "Background color." }, { "type": "int", "name": "bk_opacity", "description": "Background opacity (0-100)." }, { "type": "boolean", "name": "chatlog", "description": "Chat log." }, { "type": "int", "name": "chatlog_lines", "description": "Chat log lines." }, { "type": "int", "name": "color", "description": "Text color." }, { "type": "boolean", "name": "extents", "description": "Extents wrap." }, { "type": "int", "name": "extents_cx", "description": "Extents cx." }, { "type": "int", "name": "extents_cy", "description": "Extents cy." }, { "type": "String", "name": "file", "description": "File path name." }, { "type": "boolean", "name": "read_from_file", "description": "Read text from the specified file." }, { "type": "Object", "name": "font", "description": "Holds data for the font. Ex: `\"font\": { \"face\": \"Arial\", \"flags\": 0, \"size\": 150, \"style\": \"\" }`" }, { "type": "String", "name": "font.face", "description": "Font face." }, { "type": "int", "name": "font.flags", "description": "Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8`" }, { "type": "int", "name": "font.size", "description": "Font text size." }, { "type": "String", "name": "font.style", "description": "Font Style (unknown function)." }, { "type": "boolean", "name": "gradient", "description": "Gradient enabled." }, { "type": "int", "name": "gradient_color", "description": "Gradient color." }, { "type": "float", "name": "gradient_dir", "description": "Gradient direction." }, { "type": "int", "name": "gradient_opacity", "description": "Gradient opacity (0-100)." }, { "type": "boolean", "name": "outline", "description": "Outline." }, { "type": "int", "name": "outline_color", "description": "Outline color." }, { "type": "int", "name": "outline_size", "description": "Outline size." }, { "type": "int", "name": "outline_opacity", "description": "Outline opacity (0-100)." }, { "type": "String", "name": "text", "description": "Text content to be displayed." }, { "type": "String", "name": "valign", "description": "Text vertical alignment (\"top\", \"center\", \"bottom\")." }, { "type": "boolean", "name": "vertical", "description": "Vertical text enabled." } ], "params": [ { "type": "String", "name": "source", "description": "Source name." } ], "names": [ { "name": "", "description": "GetTextGDIPlusProperties" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "GetTextGDIPlusProperties" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Set the current properties of a Text GDI Plus source.", "param": [ "{String} `source` Name of the source.", "{String (optional)} `align` Text Alignment (\"left\", \"center\", \"right\").", "{int (optional)} `bk_color` Background color.", "{int (optional)} `bk_opacity` Background opacity (0-100).", "{boolean (optional)} `chatlog` Chat log.", "{int (optional)} `chatlog_lines` Chat log lines.", "{int (optional)} `color` Text color.", "{boolean (optional)} `extents` Extents wrap.", "{int (optional)} `extents_cx` Extents cx.", "{int (optional)} `extents_cy` Extents cy.", "{String (optional)} `file` File path name.", "{boolean (optional)} `read_from_file` Read text from the specified file.", "{Object (optional)} `font` Holds data for the font. Ex: `\"font\": { \"face\": \"Arial\", \"flags\": 0, \"size\": 150, \"style\": \"\" }`", "{String (optional)} `font.face` Font face.", "{int (optional)} `font.flags` Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8`", "{int (optional)} `font.size` Font text size.", "{String (optional)} `font.style` Font Style (unknown function).", "{boolean (optional)} `gradient` Gradient enabled.", "{int (optional)} `gradient_color` Gradient color.", "{float (optional)} `gradient_dir` Gradient direction.", "{int (optional)} `gradient_opacity` Gradient opacity (0-100).", "{boolean (optional)} `outline` Outline.", "{int (optional)} `outline_color` Outline color.", "{int (optional)} `outline_size` Outline size.", "{int (optional)} `outline_opacity` Outline opacity (0-100).", "{String (optional)} `text` Text content to be displayed.", "{String (optional)} `valign` Text vertical alignment (\"top\", \"center\", \"bottom\").", "{boolean (optional)} `vertical` Vertical text enabled.", "{boolean (optional)} `render` Visibility of the scene item." ], "api": "requests", "name": "SetTextGDIPlusProperties", "category": "sources", "since": "4.1.0", "params": [ { "type": "String", "name": "source", "description": "Name of the source." }, { "type": "String (optional)", "name": "align", "description": "Text Alignment (\"left\", \"center\", \"right\")." }, { "type": "int (optional)", "name": "bk_color", "description": "Background color." }, { "type": "int (optional)", "name": "bk_opacity", "description": "Background opacity (0-100)." }, { "type": "boolean (optional)", "name": "chatlog", "description": "Chat log." }, { "type": "int (optional)", "name": "chatlog_lines", "description": "Chat log lines." }, { "type": "int (optional)", "name": "color", "description": "Text color." }, { "type": "boolean (optional)", "name": "extents", "description": "Extents wrap." }, { "type": "int (optional)", "name": "extents_cx", "description": "Extents cx." }, { "type": "int (optional)", "name": "extents_cy", "description": "Extents cy." }, { "type": "String (optional)", "name": "file", "description": "File path name." }, { "type": "boolean (optional)", "name": "read_from_file", "description": "Read text from the specified file." }, { "type": "Object (optional)", "name": "font", "description": "Holds data for the font. Ex: `\"font\": { \"face\": \"Arial\", \"flags\": 0, \"size\": 150, \"style\": \"\" }`" }, { "type": "String (optional)", "name": "font.face", "description": "Font face." }, { "type": "int (optional)", "name": "font.flags", "description": "Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8`" }, { "type": "int (optional)", "name": "font.size", "description": "Font text size." }, { "type": "String (optional)", "name": "font.style", "description": "Font Style (unknown function)." }, { "type": "boolean (optional)", "name": "gradient", "description": "Gradient enabled." }, { "type": "int (optional)", "name": "gradient_color", "description": "Gradient color." }, { "type": "float (optional)", "name": "gradient_dir", "description": "Gradient direction." }, { "type": "int (optional)", "name": "gradient_opacity", "description": "Gradient opacity (0-100)." }, { "type": "boolean (optional)", "name": "outline", "description": "Outline." }, { "type": "int (optional)", "name": "outline_color", "description": "Outline color." }, { "type": "int (optional)", "name": "outline_size", "description": "Outline size." }, { "type": "int (optional)", "name": "outline_opacity", "description": "Outline opacity (0-100)." }, { "type": "String (optional)", "name": "text", "description": "Text content to be displayed." }, { "type": "String (optional)", "name": "valign", "description": "Text vertical alignment (\"top\", \"center\", \"bottom\")." }, { "type": "boolean (optional)", "name": "vertical", "description": "Vertical text enabled." }, { "type": "boolean (optional)", "name": "render", "description": "Visibility of the scene item." } ], "names": [ { "name": "", "description": "SetTextGDIPlusProperties" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "SetTextGDIPlusProperties" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the current properties of a Text Freetype 2 source.", "param": "{String} `source` Source name.", "return": [ "{String} `source` Source name", "{int} `color1` Gradient top color.", "{int} `color2` Gradient bottom color.", "{int} `custom_width` Custom width (0 to disable).", "{boolean} `drop_shadow` Drop shadow.", "{Object} `font` Holds data for the font. Ex: `\"font\": { \"face\": \"Arial\", \"flags\": 0, \"size\": 150, \"style\": \"\" }`", "{String} `font.face` Font face.", "{int} `font.flags` Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8`", "{int} `font.size` Font text size.", "{String} `font.style` Font Style (unknown function).", "{boolean} `from_file` Read text from the specified file.", "{boolean} `log_mode` Chat log.", "{boolean} `outline` Outline.", "{String} `text` Text content to be displayed.", "{String} `text_file` File path.", "{boolean} `word_wrap` Word wrap." ], "api": "requests", "name": "GetTextFreetype2Properties", "category": "sources", "since": "4.5.0", "returns": [ { "type": "String", "name": "source", "description": "Source name" }, { "type": "int", "name": "color1", "description": "Gradient top color." }, { "type": "int", "name": "color2", "description": "Gradient bottom color." }, { "type": "int", "name": "custom_width", "description": "Custom width (0 to disable)." }, { "type": "boolean", "name": "drop_shadow", "description": "Drop shadow." }, { "type": "Object", "name": "font", "description": "Holds data for the font. Ex: `\"font\": { \"face\": \"Arial\", \"flags\": 0, \"size\": 150, \"style\": \"\" }`" }, { "type": "String", "name": "font.face", "description": "Font face." }, { "type": "int", "name": "font.flags", "description": "Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8`" }, { "type": "int", "name": "font.size", "description": "Font text size." }, { "type": "String", "name": "font.style", "description": "Font Style (unknown function)." }, { "type": "boolean", "name": "from_file", "description": "Read text from the specified file." }, { "type": "boolean", "name": "log_mode", "description": "Chat log." }, { "type": "boolean", "name": "outline", "description": "Outline." }, { "type": "String", "name": "text", "description": "Text content to be displayed." }, { "type": "String", "name": "text_file", "description": "File path." }, { "type": "boolean", "name": "word_wrap", "description": "Word wrap." } ], "params": [ { "type": "String", "name": "source", "description": "Source name." } ], "names": [ { "name": "", "description": "GetTextFreetype2Properties" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.5.0" } ], "heading": { "level": 2, "text": "GetTextFreetype2Properties" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Set the current properties of a Text Freetype 2 source.", "param": [ "{String} `source` Source name.", "{int (optional)} `color1` Gradient top color.", "{int (optional)} `color2` Gradient bottom color.", "{int (optional)} `custom_width` Custom width (0 to disable).", "{boolean (optional)} `drop_shadow` Drop shadow.", "{Object (optional)} `font` Holds data for the font. Ex: `\"font\": { \"face\": \"Arial\", \"flags\": 0, \"size\": 150, \"style\": \"\" }`", "{String (optional)} `font.face` Font face.", "{int (optional)} `font.flags` Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8`", "{int (optional)} `font.size` Font text size.", "{String (optional)} `font.style` Font Style (unknown function).", "{boolean (optional)} `from_file` Read text from the specified file.", "{boolean (optional)} `log_mode` Chat log.", "{boolean (optional)} `outline` Outline.", "{String (optional)} `text` Text content to be displayed.", "{String (optional)} `text_file` File path.", "{boolean (optional)} `word_wrap` Word wrap." ], "api": "requests", "name": "SetTextFreetype2Properties", "category": "sources", "since": "4.5.0", "params": [ { "type": "String", "name": "source", "description": "Source name." }, { "type": "int (optional)", "name": "color1", "description": "Gradient top color." }, { "type": "int (optional)", "name": "color2", "description": "Gradient bottom color." }, { "type": "int (optional)", "name": "custom_width", "description": "Custom width (0 to disable)." }, { "type": "boolean (optional)", "name": "drop_shadow", "description": "Drop shadow." }, { "type": "Object (optional)", "name": "font", "description": "Holds data for the font. Ex: `\"font\": { \"face\": \"Arial\", \"flags\": 0, \"size\": 150, \"style\": \"\" }`" }, { "type": "String (optional)", "name": "font.face", "description": "Font face." }, { "type": "int (optional)", "name": "font.flags", "description": "Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8`" }, { "type": "int (optional)", "name": "font.size", "description": "Font text size." }, { "type": "String (optional)", "name": "font.style", "description": "Font Style (unknown function)." }, { "type": "boolean (optional)", "name": "from_file", "description": "Read text from the specified file." }, { "type": "boolean (optional)", "name": "log_mode", "description": "Chat log." }, { "type": "boolean (optional)", "name": "outline", "description": "Outline." }, { "type": "String (optional)", "name": "text", "description": "Text content to be displayed." }, { "type": "String (optional)", "name": "text_file", "description": "File path." }, { "type": "boolean (optional)", "name": "word_wrap", "description": "Word wrap." } ], "names": [ { "name": "", "description": "SetTextFreetype2Properties" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.5.0" } ], "heading": { "level": 2, "text": "SetTextFreetype2Properties" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get current properties for a Browser Source.", "param": "{String} `source` Source name.", "return": [ "{String} `source` Source name.", "{boolean} `is_local_file` Indicates that a local file is in use.", "{String} `local_file` file path.", "{String} `url` Url.", "{String} `css` CSS to inject.", "{int} `width` Width.", "{int} `height` Height.", "{int} `fps` Framerate.", "{boolean} `shutdown` Indicates whether the source should be shutdown when not visible." ], "api": "requests", "name": "GetBrowserSourceProperties", "category": "sources", "since": "4.1.0", "deprecated": "Since 4.8.0. Prefer the use of GetSourceSettings. Will be removed in v5.0.0", "returns": [ { "type": "String", "name": "source", "description": "Source name." }, { "type": "boolean", "name": "is_local_file", "description": "Indicates that a local file is in use." }, { "type": "String", "name": "local_file", "description": "file path." }, { "type": "String", "name": "url", "description": "Url." }, { "type": "String", "name": "css", "description": "CSS to inject." }, { "type": "int", "name": "width", "description": "Width." }, { "type": "int", "name": "height", "description": "Height." }, { "type": "int", "name": "fps", "description": "Framerate." }, { "type": "boolean", "name": "shutdown", "description": "Indicates whether the source should be shutdown when not visible." } ], "params": [ { "type": "String", "name": "source", "description": "Source name." } ], "names": [ { "name": "", "description": "GetBrowserSourceProperties" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "deprecateds": [ { "name": "", "description": "Since 4.8.0. Prefer the use of GetSourceSettings. Will be removed in v5.0.0" } ], "heading": { "level": 2, "text": "GetBrowserSourceProperties" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Set current properties for a Browser Source.", "param": [ "{String} `source` Name of the source.", "{boolean (optional)} `is_local_file` Indicates that a local file is in use.", "{String (optional)} `local_file` file path.", "{String (optional)} `url` Url.", "{String (optional)} `css` CSS to inject.", "{int (optional)} `width` Width.", "{int (optional)} `height` Height.", "{int (optional)} `fps` Framerate.", "{boolean (optional)} `shutdown` Indicates whether the source should be shutdown when not visible.", "{boolean (optional)} `render` Visibility of the scene item." ], "api": "requests", "name": "SetBrowserSourceProperties", "category": "sources", "deprecated": "Since 4.8.0. Prefer the use of SetSourceSettings. Will be removed in v5.0.0", "since": "4.1.0", "params": [ { "type": "String", "name": "source", "description": "Name of the source." }, { "type": "boolean (optional)", "name": "is_local_file", "description": "Indicates that a local file is in use." }, { "type": "String (optional)", "name": "local_file", "description": "file path." }, { "type": "String (optional)", "name": "url", "description": "Url." }, { "type": "String (optional)", "name": "css", "description": "CSS to inject." }, { "type": "int (optional)", "name": "width", "description": "Width." }, { "type": "int (optional)", "name": "height", "description": "Height." }, { "type": "int (optional)", "name": "fps", "description": "Framerate." }, { "type": "boolean (optional)", "name": "shutdown", "description": "Indicates whether the source should be shutdown when not visible." }, { "type": "boolean (optional)", "name": "render", "description": "Visibility of the scene item." } ], "names": [ { "name": "", "description": "SetBrowserSourceProperties" } ], "categories": [ { "name": "", "description": "sources" } ], "deprecateds": [ { "name": "", "description": "Since 4.8.0. Prefer the use of SetSourceSettings. Will be removed in v5.0.0" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "SetBrowserSourceProperties" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get configured special sources like Desktop Audio and Mic/Aux sources.", "return": [ "{String (optional)} `desktop-1` Name of the first Desktop Audio capture source.", "{String (optional)} `desktop-2` Name of the second Desktop Audio capture source.", "{String (optional)} `mic-1` Name of the first Mic/Aux input source.", "{String (optional)} `mic-2` Name of the second Mic/Aux input source.", "{String (optional)} `mic-3` NAme of the third Mic/Aux input source." ], "api": "requests", "name": "GetSpecialSources", "category": "sources", "since": "4.1.0", "returns": [ { "type": "String (optional)", "name": "desktop-1", "description": "Name of the first Desktop Audio capture source." }, { "type": "String (optional)", "name": "desktop-2", "description": "Name of the second Desktop Audio capture source." }, { "type": "String (optional)", "name": "mic-1", "description": "Name of the first Mic/Aux input source." }, { "type": "String (optional)", "name": "mic-2", "description": "Name of the second Mic/Aux input source." }, { "type": "String (optional)", "name": "mic-3", "description": "NAme of the third Mic/Aux input source." } ], "names": [ { "name": "", "description": "GetSpecialSources" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "GetSpecialSources" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "List filters applied to a source", "param": "{String} `sourceName` Source name", "return": [ "{Array} `filters` List of filters for the specified source", "{Boolean} `filters.*.enabled` Filter status (enabled or not)", "{String} `filters.*.type` Filter type", "{String} `filters.*.name` Filter name", "{Object} `filters.*.settings` Filter settings" ], "api": "requests", "name": "GetSourceFilters", "category": "sources", "since": "4.5.0", "returns": [ { "type": "Array", "name": "filters", "description": "List of filters for the specified source" }, { "type": "Boolean", "name": "filters.*.enabled", "description": "Filter status (enabled or not)" }, { "type": "String", "name": "filters.*.type", "description": "Filter type" }, { "type": "String", "name": "filters.*.name", "description": "Filter name" }, { "type": "Object", "name": "filters.*.settings", "description": "Filter settings" } ], "params": [ { "type": "String", "name": "sourceName", "description": "Source name" } ], "names": [ { "name": "", "description": "GetSourceFilters" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.5.0" } ], "heading": { "level": 2, "text": "GetSourceFilters" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "List filters applied to a source", "param": [ "{String} `sourceName` Source name", "{String} `filterName` Source filter name" ], "return": [ "{Boolean} `enabled` Filter status (enabled or not)", "{String} `type` Filter type", "{String} `name` Filter name", "{Object} `settings` Filter settings" ], "api": "requests", "name": "GetSourceFilterInfo", "category": "sources", "since": "4.7.0", "returns": [ { "type": "Boolean", "name": "enabled", "description": "Filter status (enabled or not)" }, { "type": "String", "name": "type", "description": "Filter type" }, { "type": "String", "name": "name", "description": "Filter name" }, { "type": "Object", "name": "settings", "description": "Filter settings" } ], "params": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "filterName", "description": "Source filter name" } ], "names": [ { "name": "", "description": "GetSourceFilterInfo" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.7.0" } ], "heading": { "level": 2, "text": "GetSourceFilterInfo" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Add a new filter to a source. Available source types along with their settings properties are available from `GetSourceTypesList`.", "param": [ "{String} `sourceName` Name of the source on which the filter is added", "{String} `filterName` Name of the new filter", "{String} `filterType` Filter type", "{Object} `filterSettings` Filter settings" ], "api": "requests", "name": "AddFilterToSource", "category": "sources", "since": "4.5.0", "params": [ { "type": "String", "name": "sourceName", "description": "Name of the source on which the filter is added" }, { "type": "String", "name": "filterName", "description": "Name of the new filter" }, { "type": "String", "name": "filterType", "description": "Filter type" }, { "type": "Object", "name": "filterSettings", "description": "Filter settings" } ], "names": [ { "name": "", "description": "AddFilterToSource" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.5.0" } ], "heading": { "level": 2, "text": "AddFilterToSource" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Remove a filter from a source", "param": [ "{String} `sourceName` Name of the source from which the specified filter is removed", "{String} `filterName` Name of the filter to remove" ], "api": "requests", "name": "RemoveFilterFromSource", "category": "sources", "since": "4.5.0", "params": [ { "type": "String", "name": "sourceName", "description": "Name of the source from which the specified filter is removed" }, { "type": "String", "name": "filterName", "description": "Name of the filter to remove" } ], "names": [ { "name": "", "description": "RemoveFilterFromSource" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.5.0" } ], "heading": { "level": 2, "text": "RemoveFilterFromSource" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Move a filter in the chain (absolute index positioning)", "param": [ "{String} `sourceName` Name of the source to which the filter belongs", "{String} `filterName` Name of the filter to reorder", "{Integer} `newIndex` Desired position of the filter in the chain" ], "api": "requests", "name": "ReorderSourceFilter", "category": "sources", "since": "4.5.0", "params": [ { "type": "String", "name": "sourceName", "description": "Name of the source to which the filter belongs" }, { "type": "String", "name": "filterName", "description": "Name of the filter to reorder" }, { "type": "Integer", "name": "newIndex", "description": "Desired position of the filter in the chain" } ], "names": [ { "name": "", "description": "ReorderSourceFilter" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.5.0" } ], "heading": { "level": 2, "text": "ReorderSourceFilter" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Move a filter in the chain (relative positioning)", "param": [ "{String} `sourceName` Name of the source to which the filter belongs", "{String} `filterName` Name of the filter to reorder", "{String} `movementType` How to move the filter around in the source's filter chain. Either \"up\", \"down\", \"top\" or \"bottom\"." ], "api": "requests", "name": "MoveSourceFilter", "category": "sources", "since": "4.5.0", "params": [ { "type": "String", "name": "sourceName", "description": "Name of the source to which the filter belongs" }, { "type": "String", "name": "filterName", "description": "Name of the filter to reorder" }, { "type": "String", "name": "movementType", "description": "How to move the filter around in the source's filter chain. Either \"up\", \"down\", \"top\" or \"bottom\"." } ], "names": [ { "name": "", "description": "MoveSourceFilter" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.5.0" } ], "heading": { "level": 2, "text": "MoveSourceFilter" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Update settings of a filter", "param": [ "{String} `sourceName` Name of the source to which the filter belongs", "{String} `filterName` Name of the filter to reconfigure", "{Object} `filterSettings` New settings. These will be merged to the current filter settings." ], "api": "requests", "name": "SetSourceFilterSettings", "category": "sources", "since": "4.5.0", "params": [ { "type": "String", "name": "sourceName", "description": "Name of the source to which the filter belongs" }, { "type": "String", "name": "filterName", "description": "Name of the filter to reconfigure" }, { "type": "Object", "name": "filterSettings", "description": "New settings. These will be merged to the current filter settings." } ], "names": [ { "name": "", "description": "SetSourceFilterSettings" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.5.0" } ], "heading": { "level": 2, "text": "SetSourceFilterSettings" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Change the visibility/enabled state of a filter", "param": [ "{String} `sourceName` Source name", "{String} `filterName` Source filter name", "{Boolean} `filterEnabled` New filter state" ], "api": "requests", "name": "SetSourceFilterVisibility", "category": "sources", "since": "4.7.0", "params": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "filterName", "description": "Source filter name" }, { "type": "Boolean", "name": "filterEnabled", "description": "New filter state" } ], "names": [ { "name": "", "description": "SetSourceFilterVisibility" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.7.0" } ], "heading": { "level": 2, "text": "SetSourceFilterVisibility" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the audio monitoring type of the specified source.", "param": "{String} `sourceName` Source name.", "return": "{String} `monitorType` The monitor type in use. Options: `none`, `monitorOnly`, `monitorAndOutput`.", "api": "requests", "name": "GetAudioMonitorType", "category": "sources", "since": "4.8.0", "returns": [ { "type": "String", "name": "monitorType", "description": "The monitor type in use. Options: `none`, `monitorOnly`, `monitorAndOutput`." } ], "params": [ { "type": "String", "name": "sourceName", "description": "Source name." } ], "names": [ { "name": "", "description": "GetAudioMonitorType" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.8.0" } ], "heading": { "level": 2, "text": "GetAudioMonitorType" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Set the audio monitoring type of the specified source.", "param": [ "{String} `sourceName` Source name.", "{String} `monitorType` The monitor type to use. Options: `none`, `monitorOnly`, `monitorAndOutput`." ], "api": "requests", "name": "SetAudioMonitorType", "category": "sources", "since": "4.8.0", "params": [ { "type": "String", "name": "sourceName", "description": "Source name." }, { "type": "String", "name": "monitorType", "description": "The monitor type to use. Options: `none`, `monitorOnly`, `monitorAndOutput`." } ], "names": [ { "name": "", "description": "SetAudioMonitorType" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.8.0" } ], "heading": { "level": 2, "text": "SetAudioMonitorType" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the default settings for a given source type.", "param": "{String} `sourceKind` Source kind. Also called \"source id\" in libobs terminology.", "return": [ "{String} `sourceKind` Source kind. Same value as the `sourceKind` parameter.", "{Object} `defaultSettings` Settings object for source." ], "api": "requests", "name": "GetSourceDefaultSettings", "category": "sources", "since": "4.9.0", "returns": [ { "type": "String", "name": "sourceKind", "description": "Source kind. Same value as the `sourceKind` parameter." }, { "type": "Object", "name": "defaultSettings", "description": "Settings object for source." } ], "params": [ { "type": "String", "name": "sourceKind", "description": "Source kind. Also called \"source id\" in libobs terminology." } ], "names": [ { "name": "", "description": "GetSourceDefaultSettings" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "GetSourceDefaultSettings" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "\n\nAt least `embedPictureFormat` or `saveToFilePath` must be specified.\n\nClients can specify `width` and `height` parameters to receive scaled pictures. Aspect ratio is\npreserved if only one of these two parameters is specified.", "param": [ "{String (optional)} `sourceName` Source name. Note: Since scenes are also sources, you can also provide a scene name. If not provided, the currently active scene is used.", "{String (optional)} `embedPictureFormat` Format of the Data URI encoded picture. Can be \"png\", \"jpg\", \"jpeg\" or \"bmp\" (or any other value supported by Qt's Image module)", "{String (optional)} `saveToFilePath` Full file path (file extension included) where the captured image is to be saved. Can be in a format different from `pictureFormat`. Can be a relative path.", "{String (optional)} `fileFormat` Format to save the image file as (one of the values provided in the `supported-image-export-formats` response field of `GetVersion`). If not specified, tries to guess based on file extension.", "{int (optional)} `compressionQuality` Compression ratio between -1 and 100 to write the image with. -1 is automatic, 1 is smallest file/most compression, 100 is largest file/least compression. Varies with image type.", "{int (optional)} `width` Screenshot width. Defaults to the source's base width.", "{int (optional)} `height` Screenshot height. Defaults to the source's base height." ], "return": [ "{String} `sourceName` Source name", "{String} `img` Image Data URI (if `embedPictureFormat` was specified in the request)", "{String} `imageFile` Absolute path to the saved image file (if `saveToFilePath` was specified in the request)" ], "api": "requests", "name": "TakeSourceScreenshot", "category": "sources", "since": "4.6.0", "returns": [ { "type": "String", "name": "sourceName", "description": "Source name" }, { "type": "String", "name": "img", "description": "Image Data URI (if `embedPictureFormat` was specified in the request)" }, { "type": "String", "name": "imageFile", "description": "Absolute path to the saved image file (if `saveToFilePath` was specified in the request)" } ], "params": [ { "type": "String (optional)", "name": "sourceName", "description": "Source name. Note: Since scenes are also sources, you can also provide a scene name. If not provided, the currently active scene is used." }, { "type": "String (optional)", "name": "embedPictureFormat", "description": "Format of the Data URI encoded picture. Can be \"png\", \"jpg\", \"jpeg\" or \"bmp\" (or any other value supported by Qt's Image module)" }, { "type": "String (optional)", "name": "saveToFilePath", "description": "Full file path (file extension included) where the captured image is to be saved. Can be in a format different from `pictureFormat`. Can be a relative path." }, { "type": "String (optional)", "name": "fileFormat", "description": "Format to save the image file as (one of the values provided in the `supported-image-export-formats` response field of `GetVersion`). If not specified, tries to guess based on file extension." }, { "type": "int (optional)", "name": "compressionQuality", "description": "Compression ratio between -1 and 100 to write the image with. -1 is automatic, 1 is smallest file/most compression, 100 is largest file/least compression. Varies with image type." }, { "type": "int (optional)", "name": "width", "description": "Screenshot width. Defaults to the source's base width." }, { "type": "int (optional)", "name": "height", "description": "Screenshot height. Defaults to the source's base height." } ], "names": [ { "name": "", "description": "TakeSourceScreenshot" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "TakeSourceScreenshot" }, "lead": "Takes a picture snapshot of a source and then can either or both: \t- Send it over as a Data URI (base64-encoded data) in the response (by specifying `embedPictureFormat` in the request) \t- Save it to disk (by specifying `saveToFilePath` in the request)", "type": "class", "examples": [] }, { "subheads": [], "description": "Refreshes the specified browser source.", "param": "{String} `sourceName` Source name.", "api": "requests", "name": "RefreshBrowserSource", "category": "sources", "since": "4.9.0", "params": [ { "type": "String", "name": "sourceName", "description": "Source name." } ], "names": [ { "name": "", "description": "RefreshBrowserSource" } ], "categories": [ { "name": "", "description": "sources" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "RefreshBrowserSource" }, "lead": "", "type": "class", "examples": [] } ], "outputs": [ { "subheads": [], "description": "List existing outputs", "return": "{Array} `outputs` Outputs list", "api": "requests", "name": "ListOutputs", "category": "outputs", "since": "4.7.0", "returns": [ { "type": "Array", "name": "outputs", "description": "Outputs list" } ], "names": [ { "name": "", "description": "ListOutputs" } ], "categories": [ { "name": "", "description": "outputs" } ], "sinces": [ { "name": "", "description": "4.7.0" } ], "heading": { "level": 2, "text": "ListOutputs" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "returns": [ { "type": "Output", "name": "outputInfo", "description": "Output info" } ], "params": [ { "type": "String", "name": "outputName", "description": "Output name" } ], "names": [ { "name": "", "description": "GetOutputInfo" } ], "categories": [ { "name": "", "description": "outputs" } ], "sinces": [ { "name": "", "description": "4.7.0" } ], "heading": { "level": 2, "text": "GetOutputInfo" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "\n\nNote: 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", "params": [ { "type": "String", "name": "outputName", "description": "Output name" } ], "names": [ { "name": "", "description": "StartOutput" } ], "categories": [ { "name": "", "description": "outputs" } ], "sinces": [ { "name": "", "description": "4.7.0" } ], "heading": { "level": 2, "text": "StartOutput" }, "lead": "Start an output", "type": "class", "examples": [] }, { "subheads": [], "description": "\n\nNote: 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", "{boolean (optional)} `force` Force stop (default: false)" ], "api": "requests", "name": "StopOutput", "category": "outputs", "since": "4.7.0", "params": [ { "type": "String", "name": "outputName", "description": "Output name" }, { "type": "boolean (optional)", "name": "force", "description": "Force stop (default: false)" } ], "names": [ { "name": "", "description": "StopOutput" } ], "categories": [ { "name": "", "description": "outputs" } ], "sinces": [ { "name": "", "description": "4.7.0" } ], "heading": { "level": 2, "text": "StopOutput" }, "lead": "Stop an output", "type": "class", "examples": [] } ], "profiles": [ { "subheads": [], "description": "Set the currently active profile.", "param": "{String} `profile-name` Name of the desired profile.", "api": "requests", "name": "SetCurrentProfile", "category": "profiles", "since": "4.0.0", "params": [ { "type": "String", "name": "profile-name", "description": "Name of the desired profile." } ], "names": [ { "name": "", "description": "SetCurrentProfile" } ], "categories": [ { "name": "", "description": "profiles" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "SetCurrentProfile" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "returns": [ { "type": "String", "name": "profile-name", "description": "Name of the currently active profile." } ], "names": [ { "name": "", "description": "GetCurrentProfile" } ], "categories": [ { "name": "", "description": "profiles" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "GetCurrentProfile" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get a list of available profiles.", "return": [ "{Array} `profiles` List of available profiles.", "{String} `profiles.*.profile-name` Filter name" ], "api": "requests", "name": "ListProfiles", "category": "profiles", "since": "4.0.0", "returns": [ { "type": "Array", "name": "profiles", "description": "List of available profiles." }, { "type": "String", "name": "profiles.*.profile-name", "description": "Filter name" } ], "names": [ { "name": "", "description": "ListProfiles" } ], "categories": [ { "name": "", "description": "profiles" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "ListProfiles" }, "lead": "", "type": "class", "examples": [] } ], "recording": [ { "subheads": [], "description": "Get current recording status.", "return": [ "{boolean} `isRecording` Current recording status.", "{boolean} `isRecordingPaused` Whether the recording is paused or not.", "{String (optional)} `recordTimecode` Time elapsed since recording started (only present if currently recording).", "{String (optional)} `recordingFilename` Absolute path to the recording file (only present if currently recording)." ], "api": "requests", "name": "GetRecordingStatus", "category": "recording", "since": "4.9.0", "returns": [ { "type": "boolean", "name": "isRecording", "description": "Current recording status." }, { "type": "boolean", "name": "isRecordingPaused", "description": "Whether the recording is paused or not." }, { "type": "String (optional)", "name": "recordTimecode", "description": "Time elapsed since recording started (only present if currently recording)." }, { "type": "String (optional)", "name": "recordingFilename", "description": "Absolute path to the recording file (only present if currently recording)." } ], "names": [ { "name": "", "description": "GetRecordingStatus" } ], "categories": [ { "name": "", "description": "recording" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "GetRecordingStatus" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Toggle recording on or off (depending on the current recording state).", "api": "requests", "name": "StartStopRecording", "category": "recording", "since": "0.3", "names": [ { "name": "", "description": "StartStopRecording" } ], "categories": [ { "name": "", "description": "recording" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "StartStopRecording" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Start recording.\nWill return an `error` if recording is already active.", "api": "requests", "name": "StartRecording", "category": "recording", "since": "4.1.0", "names": [ { "name": "", "description": "StartRecording" } ], "categories": [ { "name": "", "description": "recording" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "StartRecording" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Stop recording.\nWill return an `error` if recording is not active.", "api": "requests", "name": "StopRecording", "category": "recording", "since": "4.1.0", "names": [ { "name": "", "description": "StopRecording" } ], "categories": [ { "name": "", "description": "recording" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "StopRecording" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Pause the current recording.\nReturns an error if recording is not active or already paused.", "api": "requests", "name": "PauseRecording", "category": "recording", "since": "4.7.0", "names": [ { "name": "", "description": "PauseRecording" } ], "categories": [ { "name": "", "description": "recording" } ], "sinces": [ { "name": "", "description": "4.7.0" } ], "heading": { "level": 2, "text": "PauseRecording" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Resume/unpause the current recording (if paused).\nReturns an error if recording is not active or not paused.", "api": "requests", "name": "ResumeRecording", "category": "recording", "since": "4.7.0", "names": [ { "name": "", "description": "ResumeRecording" } ], "categories": [ { "name": "", "description": "recording" } ], "sinces": [ { "name": "", "description": "4.7.0" } ], "heading": { "level": 2, "text": "ResumeRecording" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "\n\nNote: If `SetRecordingFolder` is called while a recording is\nin progress, the change won't be applied immediately and will be\neffective on the next recording.", "param": "{String} `rec-folder` Path of the recording folder.", "api": "requests", "name": "SetRecordingFolder", "category": "recording", "since": "4.1.0", "params": [ { "type": "String", "name": "rec-folder", "description": "Path of the recording folder." } ], "names": [ { "name": "", "description": "SetRecordingFolder" } ], "categories": [ { "name": "", "description": "recording" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "SetRecordingFolder" }, "lead": "In the current profile, sets the recording folder of the Simple and Advanced output modes to the specified value.", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "returns": [ { "type": "String", "name": "rec-folder", "description": "Path of the recording folder." } ], "names": [ { "name": "", "description": "GetRecordingFolder" } ], "categories": [ { "name": "", "description": "recording" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "GetRecordingFolder" }, "lead": "", "type": "class", "examples": [] } ], "replay buffer": [ { "subheads": [], "description": "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", "returns": [ { "type": "boolean", "name": "isReplayBufferActive", "description": "Current recording status." } ], "names": [ { "name": "", "description": "GetReplayBufferStatus" } ], "categories": [ { "name": "", "description": "replay buffer" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "GetReplayBufferStatus" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "names": [ { "name": "", "description": "StartStopReplayBuffer" } ], "categories": [ { "name": "", "description": "replay buffer" } ], "sinces": [ { "name": "", "description": "4.2.0" } ], "heading": { "level": 2, "text": "StartStopReplayBuffer" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Start recording into the Replay Buffer.\nWill return an `error` if the Replay Buffer is already active or if the\n\"Save Replay Buffer\" hotkey is not set in OBS' settings.\nSetting this hotkey is mandatory, even when triggering saves only\nthrough obs-websocket.", "api": "requests", "name": "StartReplayBuffer", "category": "replay buffer", "since": "4.2.0", "names": [ { "name": "", "description": "StartReplayBuffer" } ], "categories": [ { "name": "", "description": "replay buffer" } ], "sinces": [ { "name": "", "description": "4.2.0" } ], "heading": { "level": 2, "text": "StartReplayBuffer" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Stop recording into the Replay Buffer.\nWill return an `error` if the Replay Buffer is not active.", "api": "requests", "name": "StopReplayBuffer", "category": "replay buffer", "since": "4.2.0", "names": [ { "name": "", "description": "StopReplayBuffer" } ], "categories": [ { "name": "", "description": "replay buffer" } ], "sinces": [ { "name": "", "description": "4.2.0" } ], "heading": { "level": 2, "text": "StopReplayBuffer" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Flush and save the contents of the Replay Buffer to disk. This is\nbasically the same as triggering the \"Save Replay Buffer\" hotkey.\nWill return an `error` if the Replay Buffer is not active.", "api": "requests", "name": "SaveReplayBuffer", "category": "replay buffer", "since": "4.2.0", "names": [ { "name": "", "description": "SaveReplayBuffer" } ], "categories": [ { "name": "", "description": "replay buffer" } ], "sinces": [ { "name": "", "description": "4.2.0" } ], "heading": { "level": 2, "text": "SaveReplayBuffer" }, "lead": "", "type": "class", "examples": [] } ], "scene collections": [ { "subheads": [], "description": "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", "params": [ { "type": "String", "name": "sc-name", "description": "Name of the desired scene collection." } ], "names": [ { "name": "", "description": "SetCurrentSceneCollection" } ], "categories": [ { "name": "", "description": "scene collections" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "SetCurrentSceneCollection" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "returns": [ { "type": "String", "name": "sc-name", "description": "Name of the currently active scene collection." } ], "names": [ { "name": "", "description": "GetCurrentSceneCollection" } ], "categories": [ { "name": "", "description": "scene collections" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "GetCurrentSceneCollection" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "List available scene collections", "return": "{Array} `scene-collections` Scene collections list", "api": "requests", "name": "ListSceneCollections", "category": "scene collections", "since": "4.0.0", "returns": [ { "type": "Array", "name": "scene-collections", "description": "Scene collections list" } ], "names": [ { "name": "", "description": "ListSceneCollections" } ], "categories": [ { "name": "", "description": "scene collections" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "ListSceneCollections" }, "lead": "", "type": "class", "examples": [] } ], "scene items": [ { "subheads": [], "description": "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", "{Array} `sceneItems` Array of scene items", "{int} `sceneItems.*.itemId` Unique item id of the source item", "{String} `sceneItems.*.sourceKind` ID if the scene item's source. For example `vlc_source` or `image_source`", "{String} `sceneItems.*.sourceName` Name of the scene item's source", "{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", "returns": [ { "type": "String", "name": "sceneName", "description": "Name of the requested (or current) scene" }, { "type": "Array", "name": "sceneItems", "description": "Array of scene items" }, { "type": "int", "name": "sceneItems.*.itemId", "description": "Unique item id of the source item" }, { "type": "String", "name": "sceneItems.*.sourceKind", "description": "ID if the scene item's source. For example `vlc_source` or `image_source`" }, { "type": "String", "name": "sceneItems.*.sourceName", "description": "Name of the scene item's source" }, { "type": "String", "name": "sceneItems.*.sourceType", "description": "Type of the scene item's source. Either `input`, `group`, or `scene`" } ], "params": [ { "type": "String (optional)", "name": "sceneName", "description": "Name of the scene to get the list of scene items from. Defaults to the current scene if not specified." } ], "names": [ { "name": "", "description": "GetSceneItemList" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "GetSceneItemList" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Gets the scene specific properties of the specified source item.\nCoordinates 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.", "{String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).", "{String (optional)} `item.name` Scene Item name (if the `item` field is an object)", "{int (optional)} `item.id` Scene Item ID (if the `item` field is an object)" ], "return": [ "{String} `name` Scene Item name.", "{int} `itemId` Scene Item ID.", "{double} `position.x` The x position of the source from the left.", "{double} `position.y` The y position of the source from the top.", "{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.", "{double} `rotation` The clockwise rotation of the item in degrees around the point of alignment.", "{double} `scale.x` The x-scale factor of the source.", "{double} `scale.y` The y-scale factor of the source.", "{int} `crop.top` The number of pixels cropped off the top of the source before scaling.", "{int} `crop.right` The number of pixels cropped off the right of the source before scaling.", "{int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling.", "{int} `crop.left` The number of pixels cropped off the left of the source before scaling.", "{bool} `visible` If the source is visible.", "{bool} `muted` If the source is muted.", "{bool} `locked` If the source's transform is locked.", "{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\".", "{int} `bounds.alignment` Alignment of the bounding box.", "{double} `bounds.x` Width of the bounding box.", "{double} `bounds.y` Height of the bounding box.", "{int} `sourceWidth` Base width (without scaling) of the source", "{int} `sourceHeight` Base source (without scaling) of the source", "{double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)", "{double} `height` Scene item height (base source height multiplied by the vertical scaling factor)", "{String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)", "{Array (optional)} `groupChildren` List of children (if this item is a group)" ], "api": "requests", "name": "GetSceneItemProperties", "category": "scene items", "since": "4.3.0", "returns": [ { "type": "String", "name": "name", "description": "Scene Item name." }, { "type": "int", "name": "itemId", "description": "Scene Item ID." }, { "type": "double", "name": "position.x", "description": "The x position of the source from the left." }, { "type": "double", "name": "position.y", "description": "The y position of the source from the top." }, { "type": "int", "name": "position.alignment", "description": "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." }, { "type": "double", "name": "rotation", "description": "The clockwise rotation of the item in degrees around the point of alignment." }, { "type": "double", "name": "scale.x", "description": "The x-scale factor of the source." }, { "type": "double", "name": "scale.y", "description": "The y-scale factor of the source." }, { "type": "int", "name": "crop.top", "description": "The number of pixels cropped off the top of the source before scaling." }, { "type": "int", "name": "crop.right", "description": "The number of pixels cropped off the right of the source before scaling." }, { "type": "int", "name": "crop.bottom", "description": "The number of pixels cropped off the bottom of the source before scaling." }, { "type": "int", "name": "crop.left", "description": "The number of pixels cropped off the left of the source before scaling." }, { "type": "bool", "name": "visible", "description": "If the source is visible." }, { "type": "bool", "name": "muted", "description": "If the source is muted." }, { "type": "bool", "name": "locked", "description": "If the source's transform is locked." }, { "type": "String", "name": "bounds.type", "description": "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\"." }, { "type": "int", "name": "bounds.alignment", "description": "Alignment of the bounding box." }, { "type": "double", "name": "bounds.x", "description": "Width of the bounding box." }, { "type": "double", "name": "bounds.y", "description": "Height of the bounding box." }, { "type": "int", "name": "sourceWidth", "description": "Base width (without scaling) of the source" }, { "type": "int", "name": "sourceHeight", "description": "Base source (without scaling) of the source" }, { "type": "double", "name": "width", "description": "Scene item width (base source width multiplied by the horizontal scaling factor)" }, { "type": "double", "name": "height", "description": "Scene item height (base source height multiplied by the vertical scaling factor)" }, { "type": "String (optional)", "name": "parentGroupName", "description": "Name of the item's parent (if this item belongs to a group)" }, { "type": "Array (optional)", "name": "groupChildren", "description": "List of children (if this item is a group)" } ], "params": [ { "type": "String (optional)", "name": "scene-name", "description": "Name of the scene the scene item belongs to. Defaults to the current scene." }, { "type": "String | Object", "name": "item", "description": "Scene Item name (if this field is a string) or specification (if it is an object)." }, { "type": "String (optional)", "name": "item.name", "description": "Scene Item name (if the `item` field is an object)" }, { "type": "int (optional)", "name": "item.id", "description": "Scene Item ID (if the `item` field is an object)" } ], "names": [ { "name": "", "description": "GetSceneItemProperties" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.3.0" } ], "heading": { "level": 2, "text": "GetSceneItemProperties" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Sets the scene specific properties of a source. Unspecified properties will remain unchanged.\nCoordinates 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.", "{String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).", "{String (optional)} `item.name` Scene Item name (if the `item` field is an object)", "{int (optional)} `item.id` Scene Item ID (if the `item` field is an object)", "{double (optional)} `position.x` The new x position of the source.", "{double (optional)} `position.y` The new y position of the source.", "{int (optional)} `position.alignment` The new alignment of the source.", "{double (optional)} `rotation` The new clockwise rotation of the item in degrees.", "{double (optional)} `scale.x` The new x scale of the item.", "{double (optional)} `scale.y` The new y scale of the item.", "{int (optional)} `crop.top` The new amount of pixels cropped off the top of the source before scaling.", "{int (optional)} `crop.bottom` The new amount of pixels cropped off the bottom of the source before scaling.", "{int (optional)} `crop.left` The new amount of pixels cropped off the left of the source before scaling.", "{int (optional)} `crop.right` The new amount of pixels cropped off the right of the source before scaling.", "{bool (optional)} `visible` The new visibility of the source. 'true' shows source, 'false' hides source.", "{bool (optional)} `locked` The new locked status of the source. 'true' keeps it in its current position, 'false' allows movement.", "{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\".", "{int (optional)} `bounds.alignment` The new alignment of the bounding box. (0-2, 4-6, 8-10)", "{double (optional)} `bounds.x` The new width of the bounding box.", "{double (optional)} `bounds.y` The new height of the bounding box." ], "api": "requests", "name": "SetSceneItemProperties", "category": "scene items", "since": "4.3.0", "params": [ { "type": "String (optional)", "name": "scene-name", "description": "Name of the scene the source item belongs to. Defaults to the current scene." }, { "type": "String | Object", "name": "item", "description": "Scene Item name (if this field is a string) or specification (if it is an object)." }, { "type": "String (optional)", "name": "item.name", "description": "Scene Item name (if the `item` field is an object)" }, { "type": "int (optional)", "name": "item.id", "description": "Scene Item ID (if the `item` field is an object)" }, { "type": "double (optional)", "name": "position.x", "description": "The new x position of the source." }, { "type": "double (optional)", "name": "position.y", "description": "The new y position of the source." }, { "type": "int (optional)", "name": "position.alignment", "description": "The new alignment of the source." }, { "type": "double (optional)", "name": "rotation", "description": "The new clockwise rotation of the item in degrees." }, { "type": "double (optional)", "name": "scale.x", "description": "The new x scale of the item." }, { "type": "double (optional)", "name": "scale.y", "description": "The new y scale of the item." }, { "type": "int (optional)", "name": "crop.top", "description": "The new amount of pixels cropped off the top of the source before scaling." }, { "type": "int (optional)", "name": "crop.bottom", "description": "The new amount of pixels cropped off the bottom of the source before scaling." }, { "type": "int (optional)", "name": "crop.left", "description": "The new amount of pixels cropped off the left of the source before scaling." }, { "type": "int (optional)", "name": "crop.right", "description": "The new amount of pixels cropped off the right of the source before scaling." }, { "type": "bool (optional)", "name": "visible", "description": "The new visibility of the source. 'true' shows source, 'false' hides source." }, { "type": "bool (optional)", "name": "locked", "description": "The new locked status of the source. 'true' keeps it in its current position, 'false' allows movement." }, { "type": "String (optional)", "name": "bounds.type", "description": "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\"." }, { "type": "int (optional)", "name": "bounds.alignment", "description": "The new alignment of the bounding box. (0-2, 4-6, 8-10)" }, { "type": "double (optional)", "name": "bounds.x", "description": "The new width of the bounding box." }, { "type": "double (optional)", "name": "bounds.y", "description": "The new height of the bounding box." } ], "names": [ { "name": "", "description": "SetSceneItemProperties" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.3.0" } ], "heading": { "level": 2, "text": "SetSceneItemProperties" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Reset a scene item.", "param": [ "{String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.", "{String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).", "{String (optional)} `item.name` Scene Item name (if the `item` field is an object)", "{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", "params": [ { "type": "String (optional)", "name": "scene-name", "description": "Name of the scene the scene item belongs to. Defaults to the current scene." }, { "type": "String | Object", "name": "item", "description": "Scene Item name (if this field is a string) or specification (if it is an object)." }, { "type": "String (optional)", "name": "item.name", "description": "Scene Item name (if the `item` field is an object)" }, { "type": "int (optional)", "name": "item.id", "description": "Scene Item ID (if the `item` field is an object)" } ], "names": [ { "name": "", "description": "ResetSceneItem" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.2.0" } ], "heading": { "level": 2, "text": "ResetSceneItem" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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.", "{String (optional)} `source` Scene Item name.", "{int (optional)} `item` Scene Item id", "{boolean} `render` true = shown ; false = hidden" ], "api": "requests", "name": "SetSceneItemRender", "category": "scene items", "since": "0.3", "params": [ { "type": "String (optional)", "name": "scene-name", "description": "Name of the scene the scene item belongs to. Defaults to the currently active scene." }, { "type": "String (optional)", "name": "source", "description": "Scene Item name." }, { "type": "int (optional)", "name": "item", "description": "Scene Item id" }, { "type": "boolean", "name": "render", "description": "true = shown ; false = hidden" } ], "names": [ { "name": "", "description": "SetSceneItemRender" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "SetSceneItemRender" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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.", "{String} `item` Scene Item name.", "{double} `x` X coordinate.", "{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.", "params": [ { "type": "String (optional)", "name": "scene-name", "description": "Name of the scene the scene item belongs to. Defaults to the current scene." }, { "type": "String", "name": "item", "description": "Scene Item name." }, { "type": "double", "name": "x", "description": "X coordinate." }, { "type": "double", "name": "y", "description": "Y coordinate." } ], "names": [ { "name": "", "description": "SetSceneItemPosition" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "deprecateds": [ { "name": "", "description": "Since 4.3.0. Prefer the use of SetSceneItemProperties." } ], "heading": { "level": 2, "text": "SetSceneItemPosition" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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.", "{String} `item` Scene Item name.", "{double} `x-scale` Width scale factor.", "{double} `y-scale` Height scale factor.", "{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.", "params": [ { "type": "String (optional)", "name": "scene-name", "description": "Name of the scene the scene item belongs to. Defaults to the current scene." }, { "type": "String", "name": "item", "description": "Scene Item name." }, { "type": "double", "name": "x-scale", "description": "Width scale factor." }, { "type": "double", "name": "y-scale", "description": "Height scale factor." }, { "type": "double", "name": "rotation", "description": "Source item rotation (in degrees)." } ], "names": [ { "name": "", "description": "SetSceneItemTransform" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "deprecateds": [ { "name": "", "description": "Since 4.3.0. Prefer the use of SetSceneItemProperties." } ], "heading": { "level": 2, "text": "SetSceneItemTransform" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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.", "{String} `item` Scene Item name.", "{int} `top` Pixel position of the top of the source item.", "{int} `bottom` Pixel position of the bottom of the source item.", "{int} `left` Pixel position of the left of the source item.", "{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.", "params": [ { "type": "String (optional)", "name": "scene-name", "description": "Name of the scene the scene item belongs to. Defaults to the current scene." }, { "type": "String", "name": "item", "description": "Scene Item name." }, { "type": "int", "name": "top", "description": "Pixel position of the top of the source item." }, { "type": "int", "name": "bottom", "description": "Pixel position of the bottom of the source item." }, { "type": "int", "name": "left", "description": "Pixel position of the left of the source item." }, { "type": "int", "name": "right", "description": "Pixel position of the right of the source item." } ], "names": [ { "name": "", "description": "SetSceneItemCrop" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "deprecateds": [ { "name": "", "description": "Since 4.3.0. Prefer the use of SetSceneItemProperties." } ], "heading": { "level": 2, "text": "SetSceneItemCrop" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Deletes a scene item.", "param": [ "{String (optional)} `scene` Name of the scene the scene item belongs to. Defaults to the current scene.", "{Object} `item` Scene item to delete (required)", "{String} `item.name` Scene Item name (prefer `id`, including both is acceptable).", "{int} `item.id` Scene Item ID." ], "api": "requests", "name": "DeleteSceneItem", "category": "scene items", "since": "4.5.0", "params": [ { "type": "String (optional)", "name": "scene", "description": "Name of the scene the scene item belongs to. Defaults to the current scene." }, { "type": "Object", "name": "item", "description": "Scene item to delete (required)" }, { "type": "String", "name": "item.name", "description": "Scene Item name (prefer `id`, including both is acceptable)." }, { "type": "int", "name": "item.id", "description": "Scene Item ID." } ], "names": [ { "name": "", "description": "DeleteSceneItem" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.5.0" } ], "heading": { "level": 2, "text": "DeleteSceneItem" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "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", "{String} `sourceName` Name of the source to be added", "{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", "returns": [ { "type": "int", "name": "itemId", "description": "Numerical ID of the created scene item" } ], "params": [ { "type": "String", "name": "sceneName", "description": "Name of the scene to create the scene item in" }, { "type": "String", "name": "sourceName", "description": "Name of the source to be added" }, { "type": "boolean", "name": "setVisible", "description": "Whether to make the sceneitem visible on creation or not. Default `true`" } ], "names": [ { "name": "", "description": "AddSceneItem" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "AddSceneItem" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Duplicates a scene item.", "param": [ "{String (optional)} `fromScene` Name of the scene to copy the item from. Defaults to the current scene.", "{String (optional)} `toScene` Name of the scene to create the item in. Defaults to the current scene.", "{Object} `item` Scene Item to duplicate from the source scene (required)", "{String} `item.name` Scene Item name (prefer `id`, including both is acceptable).", "{int} `item.id` Scene Item ID." ], "return": [ "{String} `scene` Name of the scene where the new item was created", "{Object} `item` New item info", "{int} `item.id` New item ID", "{String} `item.name` New item name" ], "api": "requests", "name": "DuplicateSceneItem", "category": "scene items", "since": "4.5.0", "returns": [ { "type": "String", "name": "scene", "description": "Name of the scene where the new item was created" }, { "type": "Object", "name": "item", "description": "New item info" }, { "type": "int", "name": "item.id", "description": "New item ID" }, { "type": "String", "name": "item.name", "description": "New item name" } ], "params": [ { "type": "String (optional)", "name": "fromScene", "description": "Name of the scene to copy the item from. Defaults to the current scene." }, { "type": "String (optional)", "name": "toScene", "description": "Name of the scene to create the item in. Defaults to the current scene." }, { "type": "Object", "name": "item", "description": "Scene Item to duplicate from the source scene (required)" }, { "type": "String", "name": "item.name", "description": "Scene Item name (prefer `id`, including both is acceptable)." }, { "type": "int", "name": "item.id", "description": "Scene Item ID." } ], "names": [ { "name": "", "description": "DuplicateSceneItem" } ], "categories": [ { "name": "", "description": "scene items" } ], "sinces": [ { "name": "", "description": "4.5.0" } ], "heading": { "level": 2, "text": "DuplicateSceneItem" }, "lead": "", "type": "class", "examples": [] } ], "scenes": [ { "subheads": [], "description": "Switch to the specified scene.", "param": "{String} `scene-name` Name of the scene to switch to.", "api": "requests", "name": "SetCurrentScene", "category": "scenes", "since": "0.3", "params": [ { "type": "String", "name": "scene-name", "description": "Name of the scene to switch to." } ], "names": [ { "name": "", "description": "SetCurrentScene" } ], "categories": [ { "name": "", "description": "scenes" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "SetCurrentScene" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the current scene's name and source items.", "return": [ "{String} `name` Name of the currently active scene.", "{Array} `sources` Ordered list of the current scene's source items." ], "api": "requests", "name": "GetCurrentScene", "category": "scenes", "since": "0.3", "returns": [ { "type": "String", "name": "name", "description": "Name of the currently active scene." }, { "type": "Array", "name": "sources", "description": "Ordered list of the current scene's source items." } ], "names": [ { "name": "", "description": "GetCurrentScene" } ], "categories": [ { "name": "", "description": "scenes" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "GetCurrentScene" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get a list of scenes in the currently active profile.", "return": [ "{String} `current-scene` Name of the currently active scene.", "{Array} `scenes` Ordered list of the current profile's scenes (See [GetCurrentScene](#getcurrentscene) for more information)." ], "api": "requests", "name": "GetSceneList", "category": "scenes", "since": "0.3", "returns": [ { "type": "String", "name": "current-scene", "description": "Name of the currently active scene." }, { "type": "Array", "name": "scenes", "description": "Ordered list of the current profile's scenes (See [GetCurrentScene](#getcurrentscene) for more information)." } ], "names": [ { "name": "", "description": "GetSceneList" } ], "categories": [ { "name": "", "description": "scenes" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "GetSceneList" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Create a new scene scene.", "param": "{String} `sceneName` Name of the scene to create.", "api": "requests", "name": "CreateScene", "category": "scenes", "since": "4.9.0", "params": [ { "type": "String", "name": "sceneName", "description": "Name of the scene to create." } ], "names": [ { "name": "", "description": "CreateScene" } ], "categories": [ { "name": "", "description": "scenes" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "CreateScene" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Changes the order of scene items in the requested scene.", "param": [ "{String (optional)} `scene` Name of the scene to reorder (defaults to current).", "{Array} `items` Ordered list of objects with name and/or id specified. Id preferred due to uniqueness per scene", "{int (optional)} `items.*.id` Id of a specific scene item. Unique on a scene by scene basis.", "{String (optional)} `items.*.name` Name of a scene item. Sufficiently unique if no scene items share sources within the scene." ], "api": "requests", "name": "ReorderSceneItems", "category": "scenes", "since": "4.5.0", "params": [ { "type": "String (optional)", "name": "scene", "description": "Name of the scene to reorder (defaults to current)." }, { "type": "Array", "name": "items", "description": "Ordered list of objects with name and/or id specified. Id preferred due to uniqueness per scene" }, { "type": "int (optional)", "name": "items.*.id", "description": "Id of a specific scene item. Unique on a scene by scene basis." }, { "type": "String (optional)", "name": "items.*.name", "description": "Name of a scene item. Sufficiently unique if no scene items share sources within the scene." } ], "names": [ { "name": "", "description": "ReorderSceneItems" } ], "categories": [ { "name": "", "description": "scenes" } ], "sinces": [ { "name": "", "description": "4.5.0" } ], "heading": { "level": 2, "text": "ReorderSceneItems" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Set a scene to use a specific transition override.", "param": [ "{String} `sceneName` Name of the scene to switch to.", "{String} `transitionName` Name of the transition to use.", "{int (Optional)} `transitionDuration` Duration in milliseconds of the transition if transition is not fixed. Defaults to the current duration specified in the UI if there is no current override and this value is not given." ], "api": "requests", "name": "SetSceneTransitionOverride", "category": "scenes", "since": "4.8.0", "params": [ { "type": "String", "name": "sceneName", "description": "Name of the scene to switch to." }, { "type": "String", "name": "transitionName", "description": "Name of the transition to use." }, { "type": "int (Optional)", "name": "transitionDuration", "description": "Duration in milliseconds of the transition if transition is not fixed. Defaults to the current duration specified in the UI if there is no current override and this value is not given." } ], "names": [ { "name": "", "description": "SetSceneTransitionOverride" } ], "categories": [ { "name": "", "description": "scenes" } ], "sinces": [ { "name": "", "description": "4.8.0" } ], "heading": { "level": 2, "text": "SetSceneTransitionOverride" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Remove any transition override on a scene.", "param": "{String} `sceneName` Name of the scene to switch to.", "api": "requests", "name": "RemoveSceneTransitionOverride", "category": "scenes", "since": "4.8.0", "params": [ { "type": "String", "name": "sceneName", "description": "Name of the scene to switch to." } ], "names": [ { "name": "", "description": "RemoveSceneTransitionOverride" } ], "categories": [ { "name": "", "description": "scenes" } ], "sinces": [ { "name": "", "description": "4.8.0" } ], "heading": { "level": 2, "text": "RemoveSceneTransitionOverride" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the current scene transition override.", "param": "{String} `sceneName` Name of the scene to switch to.", "return": [ "{String} `transitionName` Name of the current overriding transition. Empty string if no override is set.", "{int} `transitionDuration` Transition duration. `-1` if no override is set." ], "api": "requests", "name": "GetSceneTransitionOverride", "category": "scenes", "since": "4.8.0", "returns": [ { "type": "String", "name": "transitionName", "description": "Name of the current overriding transition. Empty string if no override is set." }, { "type": "int", "name": "transitionDuration", "description": "Transition duration. `-1` if no override is set." } ], "params": [ { "type": "String", "name": "sceneName", "description": "Name of the scene to switch to." } ], "names": [ { "name": "", "description": "GetSceneTransitionOverride" } ], "categories": [ { "name": "", "description": "scenes" } ], "sinces": [ { "name": "", "description": "4.8.0" } ], "heading": { "level": 2, "text": "GetSceneTransitionOverride" }, "lead": "", "type": "class", "examples": [] } ], "streaming": [ { "subheads": [], "description": "Get current streaming and recording status.", "return": [ "{boolean} `streaming` Current streaming status.", "{boolean} `recording` Current recording status.", "{boolean} `recording-paused` If recording is paused.", "{boolean} `preview-only` Always false. Retrocompatibility with OBSRemote.", "{String (optional)} `stream-timecode` Time elapsed since streaming started (only present if currently streaming).", "{String (optional)} `rec-timecode` Time elapsed since recording started (only present if currently recording)." ], "api": "requests", "name": "GetStreamingStatus", "category": "streaming", "since": "0.3", "returns": [ { "type": "boolean", "name": "streaming", "description": "Current streaming status." }, { "type": "boolean", "name": "recording", "description": "Current recording status." }, { "type": "boolean", "name": "recording-paused", "description": "If recording is paused." }, { "type": "boolean", "name": "preview-only", "description": "Always false. Retrocompatibility with OBSRemote." }, { "type": "String (optional)", "name": "stream-timecode", "description": "Time elapsed since streaming started (only present if currently streaming)." }, { "type": "String (optional)", "name": "rec-timecode", "description": "Time elapsed since recording started (only present if currently recording)." } ], "names": [ { "name": "", "description": "GetStreamingStatus" } ], "categories": [ { "name": "", "description": "streaming" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "GetStreamingStatus" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Toggle streaming on or off (depending on the current stream state).", "api": "requests", "name": "StartStopStreaming", "category": "streaming", "since": "0.3", "names": [ { "name": "", "description": "StartStopStreaming" } ], "categories": [ { "name": "", "description": "streaming" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "StartStopStreaming" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Start streaming.\nWill return an `error` if streaming is already active.", "param": [ "{Object (optional)} `stream` Special stream configuration. Note: these won't be saved to OBS' configuration.", "{String (optional)} `stream.type` If specified ensures the type of stream matches the given type (usually 'rtmp_custom' or 'rtmp_common'). If the currently configured stream type does not match the given stream type, all settings must be specified in the `settings` object or an error will occur when starting the stream.", "{Object (optional)} `stream.metadata` Adds the given object parameters as encoded query string parameters to the 'key' of the RTMP stream. Used to pass data to the RTMP service about the streaming. May be any String, Numeric, or Boolean field.", "{Object (optional)} `stream.settings` Settings for the stream.", "{String (optional)} `stream.settings.server` The publish URL.", "{String (optional)} `stream.settings.key` The publish key of the stream.", "{boolean (optional)} `stream.settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.", "{String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`.", "{String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`." ], "api": "requests", "name": "StartStreaming", "category": "streaming", "since": "4.1.0", "params": [ { "type": "Object (optional)", "name": "stream", "description": "Special stream configuration. Note: these won't be saved to OBS' configuration." }, { "type": "String (optional)", "name": "stream.type", "description": "If specified ensures the type of stream matches the given type (usually 'rtmp_custom' or 'rtmp_common'). If the currently configured stream type does not match the given stream type, all settings must be specified in the `settings` object or an error will occur when starting the stream." }, { "type": "Object (optional)", "name": "stream.metadata", "description": "Adds the given object parameters as encoded query string parameters to the 'key' of the RTMP stream. Used to pass data to the RTMP service about the streaming. May be any String, Numeric, or Boolean field." }, { "type": "Object (optional)", "name": "stream.settings", "description": "Settings for the stream." }, { "type": "String (optional)", "name": "stream.settings.server", "description": "The publish URL." }, { "type": "String (optional)", "name": "stream.settings.key", "description": "The publish key of the stream." }, { "type": "boolean (optional)", "name": "stream.settings.use_auth", "description": "Indicates whether authentication should be used when connecting to the streaming server." }, { "type": "String (optional)", "name": "stream.settings.username", "description": "If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`." }, { "type": "String (optional)", "name": "stream.settings.password", "description": "If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`." } ], "names": [ { "name": "", "description": "StartStreaming" } ], "categories": [ { "name": "", "description": "streaming" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "StartStreaming" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Stop streaming.\nWill return an `error` if streaming is not active.", "api": "requests", "name": "StopStreaming", "category": "streaming", "since": "4.1.0", "names": [ { "name": "", "description": "StopStreaming" } ], "categories": [ { "name": "", "description": "streaming" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "StopStreaming" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Sets one or more attributes of the current streaming server settings. Any options not passed will remain unchanged. Returns the updated settings in response. If 'type' is different than the current streaming service type, all settings are required. Returns the full settings of the stream (the same as GetStreamSettings).", "param": [ "{String} `type` The type of streaming service configuration, usually `rtmp_custom` or `rtmp_common`.", "{Object} `settings` The actual settings of the stream.", "{String (optional)} `settings.server` The publish URL.", "{String (optional)} `settings.key` The publish key.", "{boolean (optional)} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.", "{String (optional)} `settings.username` The username for the streaming service.", "{String (optional)} `settings.password` The password for the streaming service.", "{boolean} `save` Persist the settings to disk." ], "api": "requests", "name": "SetStreamSettings", "category": "streaming", "since": "4.1.0", "params": [ { "type": "String", "name": "type", "description": "The type of streaming service configuration, usually `rtmp_custom` or `rtmp_common`." }, { "type": "Object", "name": "settings", "description": "The actual settings of the stream." }, { "type": "String (optional)", "name": "settings.server", "description": "The publish URL." }, { "type": "String (optional)", "name": "settings.key", "description": "The publish key." }, { "type": "boolean (optional)", "name": "settings.use_auth", "description": "Indicates whether authentication should be used when connecting to the streaming server." }, { "type": "String (optional)", "name": "settings.username", "description": "The username for the streaming service." }, { "type": "String (optional)", "name": "settings.password", "description": "The password for the streaming service." }, { "type": "boolean", "name": "save", "description": "Persist the settings to disk." } ], "names": [ { "name": "", "description": "SetStreamSettings" } ], "categories": [ { "name": "", "description": "streaming" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "SetStreamSettings" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the current streaming server settings.", "return": [ "{String} `type` The type of streaming service configuration. Possible values: 'rtmp_custom' or 'rtmp_common'.", "{Object} `settings` Stream settings object.", "{String} `settings.server` The publish URL.", "{String} `settings.key` The publish key of the stream.", "{boolean} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.", "{String} `settings.username` The username to use when accessing the streaming server. Only present if `use_auth` is `true`.", "{String} `settings.password` The password to use when accessing the streaming server. Only present if `use_auth` is `true`." ], "api": "requests", "name": "GetStreamSettings", "category": "streaming", "since": "4.1.0", "returns": [ { "type": "String", "name": "type", "description": "The type of streaming service configuration. Possible values: 'rtmp_custom' or 'rtmp_common'." }, { "type": "Object", "name": "settings", "description": "Stream settings object." }, { "type": "String", "name": "settings.server", "description": "The publish URL." }, { "type": "String", "name": "settings.key", "description": "The publish key of the stream." }, { "type": "boolean", "name": "settings.use_auth", "description": "Indicates whether authentication should be used when connecting to the streaming server." }, { "type": "String", "name": "settings.username", "description": "The username to use when accessing the streaming server. Only present if `use_auth` is `true`." }, { "type": "String", "name": "settings.password", "description": "The password to use when accessing the streaming server. Only present if `use_auth` is `true`." } ], "names": [ { "name": "", "description": "GetStreamSettings" } ], "categories": [ { "name": "", "description": "streaming" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "GetStreamSettings" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Save the current streaming server settings to disk.", "api": "requests", "name": "SaveStreamSettings", "category": "streaming", "since": "4.1.0", "names": [ { "name": "", "description": "SaveStreamSettings" } ], "categories": [ { "name": "", "description": "streaming" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "SaveStreamSettings" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Send the provided text as embedded CEA-608 caption data.", "param": "{String} `text` Captions text", "api": "requests", "name": "SendCaptions", "category": "streaming", "since": "4.6.0", "params": [ { "type": "String", "name": "text", "description": "Captions text" } ], "names": [ { "name": "", "description": "SendCaptions" } ], "categories": [ { "name": "", "description": "streaming" } ], "sinces": [ { "name": "", "description": "4.6.0" } ], "heading": { "level": 2, "text": "SendCaptions" }, "lead": "", "type": "class", "examples": [] } ], "studio mode": [ { "subheads": [], "description": "Indicates if Studio Mode is currently enabled.", "return": "{boolean} `studio-mode` Indicates if Studio Mode is enabled.", "api": "requests", "name": "GetStudioModeStatus", "category": "studio mode", "since": "4.1.0", "returns": [ { "type": "boolean", "name": "studio-mode", "description": "Indicates if Studio Mode is enabled." } ], "names": [ { "name": "", "description": "GetStudioModeStatus" } ], "categories": [ { "name": "", "description": "studio mode" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "GetStudioModeStatus" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the name of the currently previewed scene and its list of sources.\nWill return an `error` if Studio Mode is not enabled.", "return": [ "{String} `name` The name of the active preview scene.", "{Array} `sources`" ], "api": "requests", "name": "GetPreviewScene", "category": "studio mode", "since": "4.1.0", "returns": [ { "type": "String", "name": "name", "description": "The name of the active preview scene." }, { "type": "Array", "name": "sources", "description": "" } ], "names": [ { "name": "", "description": "GetPreviewScene" } ], "categories": [ { "name": "", "description": "studio mode" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "GetPreviewScene" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Set the active preview scene.\nWill return an `error` if Studio Mode is not enabled.", "param": "{String} `scene-name` The name of the scene to preview.", "api": "requests", "name": "SetPreviewScene", "category": "studio mode", "since": "4.1.0", "params": [ { "type": "String", "name": "scene-name", "description": "The name of the scene to preview." } ], "names": [ { "name": "", "description": "SetPreviewScene" } ], "categories": [ { "name": "", "description": "studio mode" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "SetPreviewScene" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Transitions the currently previewed scene to the main output.\nWill return an `error` if Studio Mode is not enabled.", "param": [ "{Object (optional)} `with-transition` Change the active transition before switching scenes. Defaults to the active transition.", "{String} `with-transition.name` Name of the transition.", "{int (optional)} `with-transition.duration` Transition duration (in milliseconds)." ], "api": "requests", "name": "TransitionToProgram", "category": "studio mode", "since": "4.1.0", "params": [ { "type": "Object (optional)", "name": "with-transition", "description": "Change the active transition before switching scenes. Defaults to the active transition." }, { "type": "String", "name": "with-transition.name", "description": "Name of the transition." }, { "type": "int (optional)", "name": "with-transition.duration", "description": "Transition duration (in milliseconds)." } ], "names": [ { "name": "", "description": "TransitionToProgram" } ], "categories": [ { "name": "", "description": "studio mode" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "TransitionToProgram" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Enables Studio Mode.", "api": "requests", "name": "EnableStudioMode", "category": "studio mode", "since": "4.1.0", "names": [ { "name": "", "description": "EnableStudioMode" } ], "categories": [ { "name": "", "description": "studio mode" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "EnableStudioMode" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Disables Studio Mode.", "api": "requests", "name": "DisableStudioMode", "category": "studio mode", "since": "4.1.0", "names": [ { "name": "", "description": "DisableStudioMode" } ], "categories": [ { "name": "", "description": "studio mode" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "DisableStudioMode" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Toggles Studio Mode (depending on the current state of studio mode).", "api": "requests", "name": "ToggleStudioMode", "category": "studio mode", "since": "4.1.0", "names": [ { "name": "", "description": "ToggleStudioMode" } ], "categories": [ { "name": "", "description": "studio mode" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "ToggleStudioMode" }, "lead": "", "type": "class", "examples": [] } ], "transitions": [ { "subheads": [], "description": "List of all transitions available in the frontend's dropdown menu.", "return": [ "{String} `current-transition` Name of the currently active transition.", "{Array} `transitions` List of transitions.", "{String} `transitions.*.name` Name of the transition." ], "api": "requests", "name": "GetTransitionList", "category": "transitions", "since": "4.1.0", "returns": [ { "type": "String", "name": "current-transition", "description": "Name of the currently active transition." }, { "type": "Array", "name": "transitions", "description": "List of transitions." }, { "type": "String", "name": "transitions.*.name", "description": "Name of the transition." } ], "names": [ { "name": "", "description": "GetTransitionList" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "GetTransitionList" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the name of the currently selected transition in the frontend's dropdown menu.", "return": [ "{String} `name` Name of the selected transition.", "{int (optional)} `duration` Transition duration (in milliseconds) if supported by the transition." ], "api": "requests", "name": "GetCurrentTransition", "category": "transitions", "since": "0.3", "returns": [ { "type": "String", "name": "name", "description": "Name of the selected transition." }, { "type": "int (optional)", "name": "duration", "description": "Transition duration (in milliseconds) if supported by the transition." } ], "names": [ { "name": "", "description": "GetCurrentTransition" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "GetCurrentTransition" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Set the active transition.", "param": "{String} `transition-name` The name of the transition.", "api": "requests", "name": "SetCurrentTransition", "category": "transitions", "since": "0.3", "params": [ { "type": "String", "name": "transition-name", "description": "The name of the transition." } ], "names": [ { "name": "", "description": "SetCurrentTransition" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "0.3" } ], "heading": { "level": 2, "text": "SetCurrentTransition" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Set the duration of the currently selected transition if supported.", "param": "{int} `duration` Desired duration of the transition (in milliseconds).", "api": "requests", "name": "SetTransitionDuration", "category": "transitions", "since": "4.0.0", "params": [ { "type": "int", "name": "duration", "description": "Desired duration of the transition (in milliseconds)." } ], "names": [ { "name": "", "description": "SetTransitionDuration" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "4.0.0" } ], "heading": { "level": 2, "text": "SetTransitionDuration" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the duration of the currently selected transition if supported.", "return": "{int} `transition-duration` Duration of the current transition (in milliseconds).", "api": "requests", "name": "GetTransitionDuration", "category": "transitions", "since": "4.1.0", "returns": [ { "type": "int", "name": "transition-duration", "description": "Duration of the current transition (in milliseconds)." } ], "names": [ { "name": "", "description": "GetTransitionDuration" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "4.1.0" } ], "heading": { "level": 2, "text": "GetTransitionDuration" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the position of the current transition.", "return": "{double} `position` current transition position. This value will be between 0.0 and 1.0. Note: Transition returns 1.0 when not active.", "api": "requests", "name": "GetTransitionPosition", "category": "transitions", "since": "4.9.0", "returns": [ { "type": "double", "name": "position", "description": "current transition position. This value will be between 0.0 and 1.0. Note: Transition returns 1.0 when not active." } ], "names": [ { "name": "", "description": "GetTransitionPosition" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "GetTransitionPosition" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Get the current settings of a transition", "param": "{String} `transitionName` Transition name", "return": "{Object} `transitionSettings` Current transition settings", "api": "requests", "name": "GetTransitionSettings", "category": "transitions", "since": "4.9.0", "returns": [ { "type": "Object", "name": "transitionSettings", "description": "Current transition settings" } ], "params": [ { "type": "String", "name": "transitionName", "description": "Transition name" } ], "names": [ { "name": "", "description": "GetTransitionSettings" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "GetTransitionSettings" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Change the current settings of a transition", "param": [ "{String} `transitionName` Transition name", "{Object} `transitionSettings` Transition settings (they can be partial)" ], "return": "{Object} `transitionSettings` Updated transition settings", "api": "requests", "name": "SetTransitionSettings", "category": "transitions", "since": "4.9.0", "returns": [ { "type": "Object", "name": "transitionSettings", "description": "Updated transition settings" } ], "params": [ { "type": "String", "name": "transitionName", "description": "Transition name" }, { "type": "Object", "name": "transitionSettings", "description": "Transition settings (they can be partial)" } ], "names": [ { "name": "", "description": "SetTransitionSettings" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "SetTransitionSettings" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "Release the T-Bar (like a user releasing their mouse button after moving it).\n*YOU MUST CALL THIS if you called `SetTBarPosition` with the `release` parameter set to `false`.*", "api": "requests", "name": "ReleaseTBar", "category": "transitions", "since": "4.9.0", "names": [ { "name": "", "description": "ReleaseTBar" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "ReleaseTBar" }, "lead": "", "type": "class", "examples": [] }, { "subheads": [], "description": "\n\nIf your code needs to perform multiple successive T-Bar moves (e.g. : in an animation, or in response to a user moving a T-Bar control in your User Interface), set `release` to false and call `ReleaseTBar` later once the animation/interaction is over.", "param": [ "{double} `position` T-Bar position. This value must be between 0.0 and 1.0.", "{boolean (optional)} `release` Whether or not the T-Bar gets released automatically after setting its new position (like a user releasing their mouse button after moving the T-Bar). Call `ReleaseTBar` manually if you set `release` to false. Defaults to true." ], "api": "requests", "name": "SetTBarPosition", "category": "transitions", "since": "4.9.0", "params": [ { "type": "double", "name": "position", "description": "T-Bar position. This value must be between 0.0 and 1.0." }, { "type": "boolean (optional)", "name": "release", "description": "Whether or not the T-Bar gets released automatically after setting its new position (like a user releasing their mouse button after moving the T-Bar). Call `ReleaseTBar` manually if you set `release` to false. Defaults to true." } ], "names": [ { "name": "", "description": "SetTBarPosition" } ], "categories": [ { "name": "", "description": "transitions" } ], "sinces": [ { "name": "", "description": "4.9.0" } ], "heading": { "level": 2, "text": "SetTBarPosition" }, "lead": "Set the manual position of the T-Bar (in Studio Mode) to the specified value. Will return an error if OBS is not in studio mode or if the current transition doesn't support T-Bar control.", "type": "class", "examples": [] } ] } }obs-websocket-4.9.0/docs/generated/protocol.md000066400000000000000000003265141401107467600213650ustar00rootroot00000000000000 # 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. As of v5.0.0, backwards compatability with the protocol will not be kept. # Authentication **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. - A `salt`: applied to the password when generating the auth response. To generate the answer to the auth challenge, follow this procedure: - Concatenate the user declared password with the `salt` sent by the server (in this order: `password + server salt`). - Generate a binary SHA256 hash of the result and encode the resulting SHA256 binary hash to base64, known as a `base64 secret`. - Concatenate the base64 secret with the `challenge` sent by the server (in this order: `base64 secret + server challenge`). - Generate a binary SHA256 hash of the result and encode it to base64. - Voilà, this last base64 string is the `auth response`. You may now use it to authenticate to the server with the [`Authenticate`](#authenticate) request. Pseudo Code Example: ``` password = "supersecretpassword" challenge = "ztTBnnuqrqaKDzRM3xcVdbYm" salt = "PZVbYpvAnZut2SS6JNJytDm9" secret_string = password + salt secret_hash = binary_sha256(secret_string) secret = base64_encode(secret_hash) 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. # Table of Contents - [Typedefs](#typedefs) * [SceneItem](#sceneitem) * [SceneItemTransform](#sceneitemtransform) * [OBSStats](#obsstats) * [Output](#output) * [ScenesCollection](#scenescollection) * [Scene](#scene) - [Events](#events) * [Scenes](#scenes) + [SwitchScenes](#switchscenes) + [ScenesChanged](#sceneschanged) + [SceneCollectionChanged](#scenecollectionchanged) + [SceneCollectionListChanged](#scenecollectionlistchanged) * [Transitions](#transitions) + [SwitchTransition](#switchtransition) + [TransitionListChanged](#transitionlistchanged) + [TransitionDurationChanged](#transitiondurationchanged) + [TransitionBegin](#transitionbegin) + [TransitionEnd](#transitionend) + [TransitionVideoEnd](#transitionvideoend) * [Profiles](#profiles) + [ProfileChanged](#profilechanged) + [ProfileListChanged](#profilelistchanged) * [Streaming](#streaming) + [StreamStarting](#streamstarting) + [StreamStarted](#streamstarted) + [StreamStopping](#streamstopping) + [StreamStopped](#streamstopped) + [StreamStatus](#streamstatus) * [Recording](#recording) + [RecordingStarting](#recordingstarting) + [RecordingStarted](#recordingstarted) + [RecordingStopping](#recordingstopping) + [RecordingStopped](#recordingstopped) + [RecordingPaused](#recordingpaused) + [RecordingResumed](#recordingresumed) * [Replay Buffer](#replay-buffer) + [ReplayStarting](#replaystarting) + [ReplayStarted](#replaystarted) + [ReplayStopping](#replaystopping) + [ReplayStopped](#replaystopped) * [Other](#other) + [Exiting](#exiting) * [General](#general) + [Heartbeat](#heartbeat) + [BroadcastCustomMessage](#broadcastcustommessage) * [Sources](#sources) + [SourceCreated](#sourcecreated) + [SourceDestroyed](#sourcedestroyed) + [SourceVolumeChanged](#sourcevolumechanged) + [SourceMuteStateChanged](#sourcemutestatechanged) + [SourceAudioDeactivated](#sourceaudiodeactivated) + [SourceAudioActivated](#sourceaudioactivated) + [SourceAudioSyncOffsetChanged](#sourceaudiosyncoffsetchanged) + [SourceAudioMixersChanged](#sourceaudiomixerschanged) + [SourceRenamed](#sourcerenamed) + [SourceFilterAdded](#sourcefilteradded) + [SourceFilterRemoved](#sourcefilterremoved) + [SourceFilterVisibilityChanged](#sourcefiltervisibilitychanged) + [SourceFiltersReordered](#sourcefiltersreordered) * [Media](#media) + [MediaPlaying](#mediaplaying) + [MediaPaused](#mediapaused) + [MediaRestarted](#mediarestarted) + [MediaStopped](#mediastopped) + [MediaNext](#medianext) + [MediaPrevious](#mediaprevious) + [MediaStarted](#mediastarted) + [MediaEnded](#mediaended) * [Scene Items](#scene-items) + [SourceOrderChanged](#sourceorderchanged) + [SceneItemAdded](#sceneitemadded) + [SceneItemRemoved](#sceneitemremoved) + [SceneItemVisibilityChanged](#sceneitemvisibilitychanged) + [SceneItemLockChanged](#sceneitemlockchanged) + [SceneItemTransformChanged](#sceneitemtransformchanged) + [SceneItemSelected](#sceneitemselected) + [SceneItemDeselected](#sceneitemdeselected) * [Studio Mode](#studio-mode) + [PreviewSceneChanged](#previewscenechanged) + [StudioModeSwitched](#studiomodeswitched) - [Requests](#requests) * [General](#general-1) + [GetVersion](#getversion) + [GetAuthRequired](#getauthrequired) + [Authenticate](#authenticate) + [SetHeartbeat](#setheartbeat) + [SetFilenameFormatting](#setfilenameformatting) + [GetFilenameFormatting](#getfilenameformatting) + [GetStats](#getstats) + [BroadcastCustomMessage](#broadcastcustommessage-1) + [GetVideoInfo](#getvideoinfo) + [OpenProjector](#openprojector) + [TriggerHotkeyByName](#triggerhotkeybyname) + [TriggerHotkeyBySequence](#triggerhotkeybysequence) + [ExecuteBatch](#executebatch) * [Media Control](#media-control) + [PlayPauseMedia](#playpausemedia) + [RestartMedia](#restartmedia) + [StopMedia](#stopmedia) + [NextMedia](#nextmedia) + [PreviousMedia](#previousmedia) + [GetMediaDuration](#getmediaduration) + [GetMediaTime](#getmediatime) + [SetMediaTime](#setmediatime) + [ScrubMedia](#scrubmedia) + [GetMediaState](#getmediastate) * [Sources](#sources-1) + [GetMediaSourcesList](#getmediasourceslist) + [CreateSource](#createsource) + [GetSourcesList](#getsourceslist) + [GetSourceTypesList](#getsourcetypeslist) + [GetVolume](#getvolume) + [SetVolume](#setvolume) + [GetMute](#getmute) + [SetMute](#setmute) + [ToggleMute](#togglemute) + [GetAudioActive](#getaudioactive) + [SetSourceName](#setsourcename) + [SetSyncOffset](#setsyncoffset) + [GetSyncOffset](#getsyncoffset) + [GetSourceSettings](#getsourcesettings) + [SetSourceSettings](#setsourcesettings) + [GetTextGDIPlusProperties](#gettextgdiplusproperties) + [SetTextGDIPlusProperties](#settextgdiplusproperties) + [GetTextFreetype2Properties](#gettextfreetype2properties) + [SetTextFreetype2Properties](#settextfreetype2properties) + [GetBrowserSourceProperties](#getbrowsersourceproperties) + [SetBrowserSourceProperties](#setbrowsersourceproperties) + [GetSpecialSources](#getspecialsources) + [GetSourceFilters](#getsourcefilters) + [GetSourceFilterInfo](#getsourcefilterinfo) + [AddFilterToSource](#addfiltertosource) + [RemoveFilterFromSource](#removefilterfromsource) + [ReorderSourceFilter](#reordersourcefilter) + [MoveSourceFilter](#movesourcefilter) + [SetSourceFilterSettings](#setsourcefiltersettings) + [SetSourceFilterVisibility](#setsourcefiltervisibility) + [GetAudioMonitorType](#getaudiomonitortype) + [SetAudioMonitorType](#setaudiomonitortype) + [GetSourceDefaultSettings](#getsourcedefaultsettings) + [TakeSourceScreenshot](#takesourcescreenshot) + [RefreshBrowserSource](#refreshbrowsersource) * [Outputs](#outputs) + [ListOutputs](#listoutputs) + [GetOutputInfo](#getoutputinfo) + [StartOutput](#startoutput) + [StopOutput](#stopoutput) * [Profiles](#profiles-1) + [SetCurrentProfile](#setcurrentprofile) + [GetCurrentProfile](#getcurrentprofile) + [ListProfiles](#listprofiles) * [Recording](#recording-1) + [GetRecordingStatus](#getrecordingstatus) + [StartStopRecording](#startstoprecording) + [StartRecording](#startrecording) + [StopRecording](#stoprecording) + [PauseRecording](#pauserecording) + [ResumeRecording](#resumerecording) + [SetRecordingFolder](#setrecordingfolder) + [GetRecordingFolder](#getrecordingfolder) * [Replay Buffer](#replay-buffer-1) + [GetReplayBufferStatus](#getreplaybufferstatus) + [StartStopReplayBuffer](#startstopreplaybuffer) + [StartReplayBuffer](#startreplaybuffer) + [StopReplayBuffer](#stopreplaybuffer) + [SaveReplayBuffer](#savereplaybuffer) * [Scene Collections](#scene-collections) + [SetCurrentSceneCollection](#setcurrentscenecollection) + [GetCurrentSceneCollection](#getcurrentscenecollection) + [ListSceneCollections](#listscenecollections) * [Scene Items](#scene-items-1) + [GetSceneItemList](#getsceneitemlist) + [GetSceneItemProperties](#getsceneitemproperties) + [SetSceneItemProperties](#setsceneitemproperties) + [ResetSceneItem](#resetsceneitem) + [SetSceneItemRender](#setsceneitemrender) + [SetSceneItemPosition](#setsceneitemposition) + [SetSceneItemTransform](#setsceneitemtransform) + [SetSceneItemCrop](#setsceneitemcrop) + [DeleteSceneItem](#deletesceneitem) + [AddSceneItem](#addsceneitem) + [DuplicateSceneItem](#duplicatesceneitem) * [Scenes](#scenes-1) + [SetCurrentScene](#setcurrentscene) + [GetCurrentScene](#getcurrentscene) + [GetSceneList](#getscenelist) + [CreateScene](#createscene) + [ReorderSceneItems](#reordersceneitems) + [SetSceneTransitionOverride](#setscenetransitionoverride) + [RemoveSceneTransitionOverride](#removescenetransitionoverride) + [GetSceneTransitionOverride](#getscenetransitionoverride) * [Streaming](#streaming-1) + [GetStreamingStatus](#getstreamingstatus) + [StartStopStreaming](#startstopstreaming) + [StartStreaming](#startstreaming) + [StopStreaming](#stopstreaming) + [SetStreamSettings](#setstreamsettings) + [GetStreamSettings](#getstreamsettings) + [SaveStreamSettings](#savestreamsettings) + [SendCaptions](#sendcaptions) * [Studio Mode](#studio-mode-1) + [GetStudioModeStatus](#getstudiomodestatus) + [GetPreviewScene](#getpreviewscene) + [SetPreviewScene](#setpreviewscene) + [TransitionToProgram](#transitiontoprogram) + [EnableStudioMode](#enablestudiomode) + [DisableStudioMode](#disablestudiomode) + [ToggleStudioMode](#togglestudiomode) * [Transitions](#transitions-1) + [GetTransitionList](#gettransitionlist) + [GetCurrentTransition](#getcurrenttransition) + [SetCurrentTransition](#setcurrenttransition) + [SetTransitionDuration](#settransitionduration) + [GetTransitionDuration](#gettransitionduration) + [GetTransitionPosition](#gettransitionposition) + [GetTransitionSettings](#gettransitionsettings) + [SetTransitionSettings](#settransitionsettings) + [ReleaseTBar](#releasetbar) + [SetTBarPosition](#settbarposition) # Typedefs These are complex types, such as `Source` and `Scene`, which are used as arguments or return values in multiple requests and/or events. ## SceneItem | Name | Type | Description | | ---- | :---: | ------------| | `cy` | _Number_ | | | `cx` | _Number_ | | | `alignment` | _Number_ | 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. | | `name` | _String_ | The name of this Scene Item. | | `id` | _int_ | Scene item ID | | `render` | _Boolean_ | Whether or not this Scene Item is set to "visible". | | `muted` | _Boolean_ | Whether or not this Scene Item is muted. | | `locked` | _Boolean_ | Whether or not this Scene Item is locked and can't be moved around | | `source_cx` | _Number_ | | | `source_cy` | _Number_ | | | `type` | _String_ | Source type. Value is one of the following: "input", "filter", "transition", "scene" or "unknown" | | `volume` | _Number_ | | | `x` | _Number_ | | | `y` | _Number_ | | | `parentGroupName` | _String (optional)_ | Name of the item's parent (if this item belongs to a group) | | `groupChildren` | _Array<SceneItem> (optional)_ | List of children (if this item is a group) | ## SceneItemTransform | Name | Type | Description | | ---- | :---: | ------------| | `position.x` | _double_ | The x position of the scene item from the left. | | `position.y` | _double_ | The y position of the scene item from the top. | | `position.alignment` | _int_ | The point on the scene item that the item is manipulated from. | | `rotation` | _double_ | The clockwise rotation of the scene item in degrees around the point of alignment. | | `scale.x` | _double_ | The x-scale factor of the scene item. | | `scale.y` | _double_ | The y-scale factor of the scene item. | | `crop.top` | _int_ | The number of pixels cropped off the top of the scene item before scaling. | | `crop.right` | _int_ | The number of pixels cropped off the right of the scene item before scaling. | | `crop.bottom` | _int_ | The number of pixels cropped off the bottom of the scene item before scaling. | | `crop.left` | _int_ | The number of pixels cropped off the left of the scene item before scaling. | | `visible` | _bool_ | If the scene item is visible. | | `locked` | _bool_ | If the scene item is locked in position. | | `bounds.type` | _String_ | 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". | | `bounds.alignment` | _int_ | Alignment of the bounding box. | | `bounds.x` | _double_ | Width of the bounding box. | | `bounds.y` | _double_ | Height of the bounding box. | | `sourceWidth` | _int_ | Base width (without scaling) of the source | | `sourceHeight` | _int_ | Base source (without scaling) of the source | | `width` | _double_ | Scene item width (base source width multiplied by the horizontal scaling factor) | | `height` | _double_ | Scene item height (base source height multiplied by the vertical scaling factor) | | `parentGroupName` | _String (optional)_ | Name of the item's parent (if this item belongs to a group) | | `groupChildren` | _Array<SceneItemTransform> (optional)_ | List of children (if this item is a group) | ## OBSStats | Name | Type | Description | | ---- | :---: | ------------| | `fps` | _double_ | Current framerate. | | `render-total-frames` | _int_ | Number of frames rendered | | `render-missed-frames` | _int_ | Number of frames missed due to rendering lag | | `output-total-frames` | _int_ | Number of frames outputted | | `output-skipped-frames` | _int_ | Number of frames skipped due to encoding lag | | `average-frame-time` | _double_ | Average frame render time (in milliseconds) | | `cpu-usage` | _double_ | Current CPU usage (percentage) | | `memory-usage` | _double_ | Current RAM usage (in megabytes) | | `free-disk-space` | _double_ | Free recording disk space (in megabytes) | ## Output | Name | Type | Description | | ---- | :---: | ------------| | `name` | _String_ | Output name | | `type` | _String_ | Output type/kind | | `width` | _int_ | Video output width | | `height` | _int_ | Video output height | | `flags` | _Object_ | Output flags | | `flags.rawValue` | _int_ | Raw flags value | | `flags.audio` | _boolean_ | Output uses audio | | `flags.video` | _boolean_ | Output uses video | | `flags.encoded` | _boolean_ | Output is encoded | | `flags.multiTrack` | _boolean_ | Output uses several audio tracks | | `flags.service` | _boolean_ | Output uses a service | | `settings` | _Object_ | Output name | | `active` | _boolean_ | Output status (active or not) | | `reconnecting` | _boolean_ | Output reconnection status (reconnecting or not) | | `congestion` | _double_ | Output congestion | | `totalFrames` | _int_ | Number of frames sent | | `droppedFrames` | _int_ | Number of frames dropped | | `totalBytes` | _int_ | Total bytes sent | ## ScenesCollection | Name | Type | Description | | ---- | :---: | ------------| | `sc-name` | _String_ | Name of the scene collection | ## Scene | Name | Type | Description | | ---- | :---: | ------------| | `name` | _String_ | Name of the currently active scene. | | `sources` | _Array<SceneItem>_ | Ordered list of the current scene's source items. | # Events Events are broadcast by the server to each connected client when a recognized action occurs within OBS. An event message will contain at least the following base fields: - `update-type` _String_: the type of event. - `stream-timecode` _String (optional)_: time elapsed between now and stream start (only present if OBS Studio is streaming). - `rec-timecode` _String (optional)_: time elapsed between now and recording start (only present if OBS Studio is recording). Timecodes are sent using the format: `HH:MM:SS.mmm` Additional fields may be present in the event message depending on the event type. ## Scenes ### SwitchScenes - Added in v0.3 Indicates a scene change. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String_ | The new scene. | | `sources` | _Array<SceneItem>_ | List of scene items in the new scene. Same specification as [`GetCurrentScene`](#getcurrentscene). | --- ### ScenesChanged - Added in v0.3 Note: This event is not fired when the scenes are reordered. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `scenes` | _Array<Scene>_ | Scenes list. | --- ### SceneCollectionChanged - Added in v4.0.0 Triggered when switching to another scene collection or when renaming the current scene collection. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sceneCollection` | _String_ | Name of the new current scene collection. | --- ### SceneCollectionListChanged - Added in v4.0.0 Triggered when a scene collection is created, added, renamed, or removed. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sceneCollections` | _Array<Object>_ | Scene collections list. | | `sceneCollections.*.name` | _String_ | Scene collection name. | --- ## Transitions ### SwitchTransition - Added in v4.0.0 The active transition has been changed. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `transition-name` | _String_ | The name of the new active transition. | --- ### TransitionListChanged - Added in v4.0.0 The list of available transitions has been modified. Transitions have been added, removed, or renamed. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `transitions` | _Array<Object>_ | Transitions list. | | `transitions.*.name` | _String_ | Transition name. | --- ### TransitionDurationChanged - Added in v4.0.0 The active transition duration has been changed. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `new-duration` | _int_ | New transition duration. | --- ### TransitionBegin - Added in v4.0.0 A transition (other than "cut") has begun. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `name` | _String_ | Transition name. | | `type` | _String_ | Transition type. | | `duration` | _int_ | Transition duration (in milliseconds). Will be -1 for any transition with a fixed duration, such as a Stinger, due to limitations of the OBS API. | | `from-scene` | _String_ | Source scene of the transition | | `to-scene` | _String_ | Destination scene of the transition | --- ### TransitionEnd - Added in v4.8.0 A transition (other than "cut") has ended. Note: The `from-scene` field is not available in TransitionEnd. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `name` | _String_ | Transition name. | | `type` | _String_ | Transition type. | | `duration` | _int_ | Transition duration (in milliseconds). | | `to-scene` | _String_ | Destination scene of the transition | --- ### TransitionVideoEnd - Added in v4.8.0 A stinger transition has finished playing its video. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `name` | _String_ | Transition name. | | `type` | _String_ | Transition type. | | `duration` | _int_ | Transition duration (in milliseconds). | | `from-scene` | _String_ | Source scene of the transition | | `to-scene` | _String_ | Destination scene of the transition | --- ## Profiles ### ProfileChanged - Added in v4.0.0 Triggered when switching to another profile or when renaming the current profile. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `profile` | _String_ | Name of the new current profile. | --- ### ProfileListChanged - Added in v4.0.0 Triggered when a profile is created, added, renamed, or removed. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `profiles` | _Array<Object>_ | Profiles list. | | `profiles.*.name` | _String_ | Profile name. | --- ## Streaming ### StreamStarting - Added in v0.3 A request to start streaming has been issued. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `preview-only` | _boolean_ | Always false (retrocompatibility). | --- ### StreamStarted - Added in v0.3 Streaming started successfully. **Response Items:** _No additional response items._ --- ### StreamStopping - Added in v0.3 A request to stop streaming has been issued. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `preview-only` | _boolean_ | Always false (retrocompatibility). | --- ### StreamStopped - Added in v0.3 Streaming stopped successfully. **Response Items:** _No additional response items._ --- ### StreamStatus - Added in v0.3 Emitted every 2 seconds when stream is active. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `streaming` | _boolean_ | Current streaming state. | | `recording` | _boolean_ | Current recording state. | | `replay-buffer-active` | _boolean_ | Replay Buffer status | | `bytes-per-sec` | _int_ | Amount of data per second (in bytes) transmitted by the stream encoder. | | `kbits-per-sec` | _int_ | Amount of data per second (in kilobits) transmitted by the stream encoder. | | `strain` | _double_ | Percentage of dropped frames. | | `total-stream-time` | _int_ | Total time (in seconds) since the stream started. | | `num-total-frames` | _int_ | Total number of frames transmitted since the stream started. | | `num-dropped-frames` | _int_ | Number of frames dropped by the encoder since the stream started. | | `fps` | _double_ | Current framerate. | | `render-total-frames` | _int_ | Number of frames rendered | | `render-missed-frames` | _int_ | Number of frames missed due to rendering lag | | `output-total-frames` | _int_ | Number of frames outputted | | `output-skipped-frames` | _int_ | Number of frames skipped due to encoding lag | | `average-frame-time` | _double_ | Average frame time (in milliseconds) | | `cpu-usage` | _double_ | Current CPU usage (percentage) | | `memory-usage` | _double_ | Current RAM usage (in megabytes) | | `free-disk-space` | _double_ | Free recording disk space (in megabytes) | | `preview-only` | _boolean_ | Always false (retrocompatibility). | --- ## Recording ### RecordingStarting - Added in v0.3 Note: `recordingFilename` is not provided in this event because this information is not available at the time this event is emitted. **Response Items:** _No additional response items._ --- ### RecordingStarted - Added in v0.3 Recording started successfully. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `recordingFilename` | _String_ | Absolute path to the file of the current recording. | --- ### RecordingStopping - Added in v0.3 A request to stop recording has been issued. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `recordingFilename` | _String_ | Absolute path to the file of the current recording. | --- ### RecordingStopped - Added in v0.3 Recording stopped successfully. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `recordingFilename` | _String_ | Absolute path to the file of the current recording. | --- ### RecordingPaused - Added in v4.7.0 Current recording paused **Response Items:** _No additional response items._ --- ### RecordingResumed - Added in v4.7.0 Current recording resumed **Response Items:** _No additional response items._ --- ## Replay Buffer ### ReplayStarting - Added in v4.2.0 A request to start the replay buffer has been issued. **Response Items:** _No additional response items._ --- ### ReplayStarted - Added in v4.2.0 Replay Buffer started successfully **Response Items:** _No additional response items._ --- ### ReplayStopping - Added in v4.2.0 A request to stop the replay buffer has been issued. **Response Items:** _No additional response items._ --- ### ReplayStopped - Added in v4.2.0 Replay Buffer stopped successfully **Response Items:** _No additional response items._ --- ## Other ### Exiting - Added in v0.3 OBS is exiting. **Response Items:** _No additional response items._ --- ## General ### Heartbeat - Added in vv0.3 Emitted every 2 seconds after enabling it by calling SetHeartbeat. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `pulse` | _boolean_ | Toggles between every JSON message as an "I am alive" indicator. | | `current-profile` | _string (optional)_ | Current active profile. | | `current-scene` | _string (optional)_ | Current active scene. | | `streaming` | _boolean (optional)_ | Current streaming state. | | `total-stream-time` | _int (optional)_ | Total time (in seconds) since the stream started. | | `total-stream-bytes` | _int (optional)_ | Total bytes sent since the stream started. | | `total-stream-frames` | _int (optional)_ | Total frames streamed since the stream started. | | `recording` | _boolean (optional)_ | Current recording state. | | `total-record-time` | _int (optional)_ | Total time (in seconds) since recording started. | | `total-record-bytes` | _int (optional)_ | Total bytes recorded since the recording started. | | `total-record-frames` | _int (optional)_ | Total frames recorded since the recording started. | | `stats` | _OBSStats_ | OBS Stats | --- ### BroadcastCustomMessage - Added in v4.7.0 A custom broadcast message, sent by the server, requested by one of the websocket clients. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `realm` | _String_ | Identifier provided by the sender | | `data` | _Object_ | User-defined data | --- ## Sources ### SourceCreated - Added in v4.6.0 A source has been created. A source can be an input, a scene or a transition. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `sourceType` | _String_ | Source type. Can be "input", "scene", "transition" or "filter". | | `sourceKind` | _String_ | Source kind. | | `sourceSettings` | _Object_ | Source settings | --- ### SourceDestroyed - Added in v4.6.0 A source has been destroyed/removed. A source can be an input, a scene or a transition. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `sourceType` | _String_ | Source type. Can be "input", "scene", "transition" or "filter". | | `sourceKind` | _String_ | Source kind. | --- ### SourceVolumeChanged - Added in v4.6.0 The volume of a source has changed. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `volume` | _float_ | Source volume | --- ### SourceMuteStateChanged - Added in v4.6.0 A source has been muted or unmuted. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `muted` | _boolean_ | Mute status of the source | --- ### SourceAudioDeactivated - Added in v4.9.0 A source has removed audio. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | --- ### SourceAudioActivated - Added in v4.9.0 A source has added audio. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | --- ### SourceAudioSyncOffsetChanged - Added in v4.6.0 The audio sync offset of a source has changed. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `syncOffset` | _int_ | Audio sync offset of the source (in nanoseconds) | --- ### SourceAudioMixersChanged - Added in v4.6.0 Audio mixer routing changed on a source. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `mixers` | _Array<Object>_ | Routing status of the source for each audio mixer (array of 6 values) | | `mixers.*.id` | _int_ | Mixer number | | `mixers.*.enabled` | _boolean_ | Routing status | | `hexMixersValue` | _String_ | Raw mixer flags (little-endian, one bit per mixer) as an hexadecimal value | --- ### SourceRenamed - Added in v4.6.0 A source has been renamed. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `previousName` | _String_ | Previous source name | | `newName` | _String_ | New source name | | `sourceType` | _String_ | Type of source (input, scene, filter, transition) | --- ### SourceFilterAdded - Added in v4.6.0 A filter was added to a source. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `filterName` | _String_ | Filter name | | `filterType` | _String_ | Filter type | | `filterSettings` | _Object_ | Filter settings | --- ### SourceFilterRemoved - Added in v4.6.0 A filter was removed from a source. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `filterName` | _String_ | Filter name | | `filterType` | _String_ | Filter type | --- ### SourceFilterVisibilityChanged - Added in v4.7.0 The visibility/enabled state of a filter changed **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `filterName` | _String_ | Filter name | | `filterEnabled` | _Boolean_ | New filter state | --- ### SourceFiltersReordered - Added in v4.6.0 Filters in a source have been reordered. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `filters` | _Array<Object>_ | Ordered Filters list | | `filters.*.name` | _String_ | Filter name | | `filters.*.type` | _String_ | Filter type | | `filters.*.enabled` | _boolean_ | Filter visibility status | --- ## Media ### MediaPlaying - Added in v4.9.0 Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | --- ### MediaPaused - Added in v4.9.0 Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | --- ### MediaRestarted - Added in v4.9.0 Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | --- ### MediaStopped - Added in v4.9.0 Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | --- ### MediaNext - Added in v4.9.0 Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | --- ### MediaPrevious - Added in v4.9.0 Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | --- ### MediaStarted - Added in v4.9.0 Note: These events are emitted by the OBS sources themselves. For example when the media file starts playing. The behavior depends on the type of media source being used. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | --- ### MediaEnded - Added in v4.9.0 Note: These events are emitted by the OBS sources themselves. For example when the media file ends. The behavior depends on the type of media source being used. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | --- ## Scene Items ### SourceOrderChanged - Added in v4.0.0 Scene items within a scene have been reordered. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String_ | Name of the scene where items have been reordered. | | `scene-items` | _Array<Object>_ | Ordered list of scene items | | `scene-items.*.source-name` | _String_ | Item source name | | `scene-items.*.item-id` | _int_ | Scene item unique ID | --- ### SceneItemAdded - Added in v4.0.0 A scene item has been added to a scene. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String_ | Name of the scene. | | `item-name` | _String_ | Name of the item added to the scene. | | `item-id` | _int_ | Scene item ID | --- ### SceneItemRemoved - Added in v4.0.0 A scene item has been removed from a scene. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String_ | Name of the scene. | | `item-name` | _String_ | Name of the item removed from the scene. | | `item-id` | _int_ | Scene item ID | --- ### SceneItemVisibilityChanged - Added in v4.0.0 A scene item's visibility has been toggled. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String_ | Name of the scene. | | `item-name` | _String_ | Name of the item in the scene. | | `item-id` | _int_ | Scene item ID | | `item-visible` | _boolean_ | New visibility state of the item. | --- ### SceneItemLockChanged - Added in v4.8.0 A scene item's locked status has been toggled. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String_ | Name of the scene. | | `item-name` | _String_ | Name of the item in the scene. | | `item-id` | _int_ | Scene item ID | | `item-locked` | _boolean_ | New locked state of the item. | --- ### SceneItemTransformChanged - Added in v4.6.0 A scene item's transform has been changed. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String_ | Name of the scene. | | `item-name` | _String_ | Name of the item in the scene. | | `item-id` | _int_ | Scene item ID | | `transform` | _SceneItemTransform_ | Scene item transform properties | --- ### SceneItemSelected - Added in v4.6.0 A scene item is selected. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String_ | Name of the scene. | | `item-name` | _String_ | Name of the item in the scene. | | `item-id` | _int_ | Name of the item in the scene. | --- ### SceneItemDeselected - Added in v4.6.0 A scene item is deselected. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String_ | Name of the scene. | | `item-name` | _String_ | Name of the item in the scene. | | `item-id` | _int_ | Name of the item in the scene. | --- ## Studio Mode ### PreviewSceneChanged - Added in v4.1.0 The selected preview scene has changed (only available in Studio Mode). **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String_ | Name of the scene being previewed. | | `sources` | _Array<SceneItem>_ | List of sources composing the scene. Same specification as [`GetCurrentScene`](#getcurrentscene). | --- ### StudioModeSwitched - Added in v4.1.0 Studio Mode has been enabled or disabled. **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `new-state` | _boolean_ | The new enabled state of Studio Mode. | --- # Requests Requests are sent by the client and require at least the following two fields: - `request-type` _String_: String name of the request type. - `message-id` _String_: Client defined identifier for the message, will be echoed in the response. 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 (Optional)_: An error message accompanying an `error` status. Additional information may be required/returned depending on the request type. See below for more information. ## General ### GetVersion - Added in v0.3 Returns the latest version of the plugin and the API. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `version` | _double_ | OBSRemote compatible API version. Fixed to 1.1 for retrocompatibility. | | `obs-websocket-version` | _String_ | obs-websocket plugin version. | | `obs-studio-version` | _String_ | OBS Studio program version. | | `available-requests` | _String_ | List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3"). | | `supported-image-export-formats` | _String_ | List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string | --- ### GetAuthRequired - Added in v0.3 Tells the client if authentication is required. If so, returns authentication parameters `challenge` and `salt` (see "Authentication" for more information). **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `authRequired` | _boolean_ | Indicates whether authentication is required. | | `challenge` | _String (optional)_ | | | `salt` | _String (optional)_ | | --- ### Authenticate - Added in v0.3 Attempt to authenticate the client to the server. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `auth` | _String_ | Response to the auth challenge (see "Authentication" for more information). | **Response Items:** _No additional response items._ --- ### SetHeartbeat - **⚠️ Deprecated. Since 4.9.0. Please poll the appropriate data using requests. Will be removed in v5.0.0. ⚠️** - Added in v4.3.0 Enable/disable sending of the Heartbeat event **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `enable` | _boolean_ | Starts/Stops emitting heartbeat messages | **Response Items:** _No additional response items._ --- ### SetFilenameFormatting - Added in v4.3.0 Set the filename formatting string **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `filename-formatting` | _String_ | Filename formatting string to set. | **Response Items:** _No additional response items._ --- ### GetFilenameFormatting - Added in v4.3.0 Get the filename formatting string **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `filename-formatting` | _String_ | Current filename formatting string. | --- ### GetStats - Added in v4.6.0 Get OBS stats (almost the same info as provided in OBS' stats window) **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `stats` | _OBSStats_ | [OBS stats](#obsstats) | --- ### BroadcastCustomMessage - Added in v4.7.0 Broadcast custom message to all connected WebSocket clients **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `realm` | _String_ | Identifier to be choosen by the client | | `data` | _Object_ | User-defined data | **Response Items:** _No additional response items._ --- ### GetVideoInfo - Added in v4.6.0 Get basic OBS video information **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `baseWidth` | _int_ | Base (canvas) width | | `baseHeight` | _int_ | Base (canvas) height | | `outputWidth` | _int_ | Output width | | `outputHeight` | _int_ | Output height | | `scaleType` | _String_ | Scaling method used if output size differs from base size | | `fps` | _double_ | Frames rendered per second | | `videoFormat` | _String_ | Video color format | | `colorSpace` | _String_ | Color space for YUV | | `colorRange` | _String_ | Color range (full or partial) | --- ### OpenProjector - Added in v4.8.0 Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `type` | _String (Optional)_ | Type of projector: `Preview` (default), `Source`, `Scene`, `StudioProgram`, or `Multiview` (case insensitive). | | `monitor` | _int (Optional)_ | Monitor to open the projector on. If -1 or omitted, opens a window. | | `geometry` | _String (Optional)_ | 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. | | `name` | _String (Optional)_ | Name of the source or scene to be displayed (ignored for other projector types). | **Response Items:** _No additional response items._ --- ### TriggerHotkeyByName - Added in v4.9.0 Executes hotkey routine, identified by hotkey unique name **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `hotkeyName` | _String_ | Unique name of the hotkey, as defined when registering the hotkey (e.g. "ReplayBuffer.Save") | **Response Items:** _No additional response items._ --- ### TriggerHotkeyBySequence - Added in v4.9.0 Executes hotkey routine, identified by bound combination of keys. A single key combination might trigger multiple hotkey routines depending on user settings **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `keyId` | _String_ | 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) | | `keyModifiers` | _Object (Optional)_ | Optional key modifiers object. False entries can be ommitted | | `keyModifiers.shift` | _boolean_ | Trigger Shift Key | | `keyModifiers.alt` | _boolean_ | Trigger Alt Key | | `keyModifiers.control` | _boolean_ | Trigger Control (Ctrl) Key | | `keyModifiers.command` | _boolean_ | Trigger Command Key (Mac) | **Response Items:** _No additional response items._ --- ### ExecuteBatch - Added in v4.9.0 Executes a list of requests sequentially (one-by-one on the same thread). **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `requests` | _Array<Object>_ | Array of requests to perform. Executed in order. | | `requests.*.request-type` | _String_ | Request type. Eg. `GetVersion`. | | `requests.*.message-id` | _String (Optional)_ | ID of the individual request. Can be any string and not required to be unique. Defaults to empty string if not specified. | | `abortOnFail` | _boolean (Optional)_ | Stop processing batch requests if one returns a failure. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `results` | _Array<Object>_ | Batch requests results, ordered sequentially. | | `results.*.message-id` | _String_ | ID of the individual request which was originally provided by the client. | | `results.*.status` | _String_ | Status response as string. Either `ok` or `error`. | | `results.*.error` | _String (Optional)_ | Error message accompanying an `error` status. | --- ## Media Control ### PlayPauseMedia - Added in v4.9.0 Pause or play a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | | `playPause` | _boolean_ | Whether to pause or play the source. `false` for play, `true` for pause. | **Response Items:** _No additional response items._ --- ### RestartMedia - Added in v4.9.0 Restart a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | **Response Items:** _No additional response items._ --- ### StopMedia - Added in v4.9.0 Stop a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | **Response Items:** _No additional response items._ --- ### NextMedia - Added in v4.9.0 Skip to the next media item in the playlist. Supports only vlc media source (as of OBS v25.0.8) **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | **Response Items:** _No additional response items._ --- ### PreviousMedia - Added in v4.9.0 Go to the previous media item in the playlist. Supports only vlc media source (as of OBS v25.0.8) **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | **Response Items:** _No additional response items._ --- ### GetMediaDuration - Added in v4.9.0 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. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `mediaDuration` | _int_ | The total length of media in milliseconds.. | --- ### GetMediaTime - Added in v4.9.0 Get the current timestamp of media in milliseconds. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `timestamp` | _int_ | The time in milliseconds since the start of the media. | --- ### SetMediaTime - Added in v4.9.0 Set the timestamp of a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | | `timestamp` | _int_ | Milliseconds to set the timestamp to. | **Response Items:** _No additional response items._ --- ### ScrubMedia - Added in v4.9.0 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. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | | `timeOffset` | _int_ | Millisecond offset (positive or negative) to offset the current media position. | **Response Items:** _No additional response items._ --- ### GetMediaState - Added in v4.9.0 Get the current playing state of a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `mediaState` | _String_ | The media state of the provided source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown` | --- ## Sources ### GetMediaSourcesList - Added in v4.9.0 List the media state of all media sources (vlc and media source) **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `mediaSources` | _Array<Object>_ | Array of sources | | `mediaSources.*.sourceName` | _String_ | Unique source name | | `mediaSources.*.sourceKind` | _String_ | Unique source internal type (a.k.a `ffmpeg_source` or `vlc_source`) | | `mediaSources.*.mediaState` | _String_ | The current state of media for that source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown` | --- ### CreateSource - Added in v4.9.0 Create a source and add it as a sceneitem to a scene. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | | `sourceKind` | _String_ | Source kind, Eg. `vlc_source`. | | `sceneName` | _String_ | Scene to add the new source to. | | `sourceSettings` | _Object (optional)_ | Source settings data. | | `setVisible` | _boolean (optional)_ | Set the created SceneItem as visible or not. Defaults to true | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `itemId` | _int_ | ID of the SceneItem in the scene. | --- ### GetSourcesList - Added in v4.3.0 List all sources available in the running OBS instance **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sources` | _Array<Object>_ | Array of sources | | `sources.*.name` | _String_ | Unique source name | | `sources.*.typeId` | _String_ | Non-unique source internal type (a.k.a kind) | | `sources.*.type` | _String_ | Source type. Value is one of the following: "input", "filter", "transition", "scene" or "unknown" | --- ### GetSourceTypesList - Added in v4.3.0 Get a list of all available sources types **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `types` | _Array<Object>_ | Array of source types | | `types.*.typeId` | _String_ | Non-unique internal source type ID | | `types.*.displayName` | _String_ | Display name of the source type | | `types.*.type` | _String_ | Type. Value is one of the following: "input", "filter", "transition" or "other" | | `types.*.defaultSettings` | _Object_ | Default settings of this source type | | `types.*.caps` | _Object_ | Source type capabilities | | `types.*.caps.isAsync` | _Boolean_ | True if source of this type provide frames asynchronously | | `types.*.caps.hasVideo` | _Boolean_ | True if sources of this type provide video | | `types.*.caps.hasAudio` | _Boolean_ | True if sources of this type provide audio | | `types.*.caps.canInteract` | _Boolean_ | True if interaction with this sources of this type is possible | | `types.*.caps.isComposite` | _Boolean_ | True if sources of this type composite one or more sub-sources | | `types.*.caps.doNotDuplicate` | _Boolean_ | True if sources of this type should not be fully duplicated | | `types.*.caps.doNotSelfMonitor` | _Boolean_ | True if sources of this type may cause a feedback loop if it's audio is monitored and shouldn't be | --- ### GetVolume - Added in v4.0.0 Get the volume of the specified source. Default response uses mul format, NOT SLIDER PERCENTAGE. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name. | | `useDecibel` | _boolean (optional)_ | Output volume in decibels of attenuation instead of amplitude/mul. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `name` | _String_ | Source name. | | `volume` | _double_ | Volume of the source. Between `0.0` and `20.0` if using mul, under `26.0` if using dB. | | `muted` | _boolean_ | Indicates whether the source is muted. | --- ### SetVolume - Added in v4.0.0 Set the volume of the specified source. Default request format uses mul, NOT SLIDER PERCENTAGE. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name. | | `volume` | _double_ | Desired volume. Must be between `0.0` and `20.0` for mul, and under 26.0 for dB. OBS will interpret dB values under -100.0 as Inf. Note: The OBS volume sliders only reach a maximum of 1.0mul/0.0dB, however OBS actually supports larger values. | | `useDecibel` | _boolean (optional)_ | Interperet `volume` data as decibels instead of amplitude/mul. | **Response Items:** _No additional response items._ --- ### GetMute - Added in v4.0.0 Get the mute status of a specified source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `name` | _String_ | Source name. | | `muted` | _boolean_ | Mute status of the source. | --- ### SetMute - Added in v4.0.0 Sets the mute status of a specified source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name. | | `mute` | _boolean_ | Desired mute status. | **Response Items:** _No additional response items._ --- ### ToggleMute - Added in v4.0.0 Inverts the mute status of a specified source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name. | **Response Items:** _No additional response items._ --- ### GetAudioActive - Added in v4.9.0 Get the audio's active status of a specified source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `audioActive` | _boolean_ | Audio active status of the source. | --- ### SetSourceName - Added in v4.8.0 Note: If the new name already exists as a source, obs-websocket will return an error. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | | `newName` | _String_ | New source name. | **Response Items:** _No additional response items._ --- ### SetSyncOffset - Added in v4.2.0 Set the audio sync offset of a specified source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name. | | `offset` | _int_ | The desired audio sync offset (in nanoseconds). | **Response Items:** _No additional response items._ --- ### GetSyncOffset - Added in v4.2.0 Get the audio sync offset of a specified source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `name` | _String_ | Source name. | | `offset` | _int_ | The audio sync offset (in nanoseconds). | --- ### GetSourceSettings - Added in v4.3.0 Get settings of the specified source **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | | `sourceType` | _String (optional)_ | Type of the specified source. Useful for type-checking if you expect a specific settings schema. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `sourceType` | _String_ | Type of the specified source | | `sourceSettings` | _Object_ | Source settings (varies between source types, may require some probing around). | --- ### SetSourceSettings - Added in v4.3.0 Set settings of the specified source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | | `sourceType` | _String (optional)_ | Type of the specified source. Useful for type-checking to avoid settings a set of settings incompatible with the actual source's type. | | `sourceSettings` | _Object_ | Source settings (varies between source types, may require some probing around). | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `sourceType` | _String_ | Type of the specified source | | `sourceSettings` | _Object_ | Updated source settings | --- ### GetTextGDIPlusProperties - Added in v4.1.0 Get the current properties of a Text GDI Plus source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name. | | `align` | _String_ | Text Alignment ("left", "center", "right"). | | `bk_color` | _int_ | Background color. | | `bk_opacity` | _int_ | Background opacity (0-100). | | `chatlog` | _boolean_ | Chat log. | | `chatlog_lines` | _int_ | Chat log lines. | | `color` | _int_ | Text color. | | `extents` | _boolean_ | Extents wrap. | | `extents_cx` | _int_ | Extents cx. | | `extents_cy` | _int_ | Extents cy. | | `file` | _String_ | File path name. | | `read_from_file` | _boolean_ | Read text from the specified file. | | `font` | _Object_ | Holds data for the font. Ex: `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }` | | `font.face` | _String_ | Font face. | | `font.flags` | _int_ | Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8` | | `font.size` | _int_ | Font text size. | | `font.style` | _String_ | Font Style (unknown function). | | `gradient` | _boolean_ | Gradient enabled. | | `gradient_color` | _int_ | Gradient color. | | `gradient_dir` | _float_ | Gradient direction. | | `gradient_opacity` | _int_ | Gradient opacity (0-100). | | `outline` | _boolean_ | Outline. | | `outline_color` | _int_ | Outline color. | | `outline_size` | _int_ | Outline size. | | `outline_opacity` | _int_ | Outline opacity (0-100). | | `text` | _String_ | Text content to be displayed. | | `valign` | _String_ | Text vertical alignment ("top", "center", "bottom"). | | `vertical` | _boolean_ | Vertical text enabled. | --- ### SetTextGDIPlusProperties - Added in v4.1.0 Set the current properties of a Text GDI Plus source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Name of the source. | | `align` | _String (optional)_ | Text Alignment ("left", "center", "right"). | | `bk_color` | _int (optional)_ | Background color. | | `bk_opacity` | _int (optional)_ | Background opacity (0-100). | | `chatlog` | _boolean (optional)_ | Chat log. | | `chatlog_lines` | _int (optional)_ | Chat log lines. | | `color` | _int (optional)_ | Text color. | | `extents` | _boolean (optional)_ | Extents wrap. | | `extents_cx` | _int (optional)_ | Extents cx. | | `extents_cy` | _int (optional)_ | Extents cy. | | `file` | _String (optional)_ | File path name. | | `read_from_file` | _boolean (optional)_ | Read text from the specified file. | | `font` | _Object (optional)_ | Holds data for the font. Ex: `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }` | | `font.face` | _String (optional)_ | Font face. | | `font.flags` | _int (optional)_ | Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8` | | `font.size` | _int (optional)_ | Font text size. | | `font.style` | _String (optional)_ | Font Style (unknown function). | | `gradient` | _boolean (optional)_ | Gradient enabled. | | `gradient_color` | _int (optional)_ | Gradient color. | | `gradient_dir` | _float (optional)_ | Gradient direction. | | `gradient_opacity` | _int (optional)_ | Gradient opacity (0-100). | | `outline` | _boolean (optional)_ | Outline. | | `outline_color` | _int (optional)_ | Outline color. | | `outline_size` | _int (optional)_ | Outline size. | | `outline_opacity` | _int (optional)_ | Outline opacity (0-100). | | `text` | _String (optional)_ | Text content to be displayed. | | `valign` | _String (optional)_ | Text vertical alignment ("top", "center", "bottom"). | | `vertical` | _boolean (optional)_ | Vertical text enabled. | | `render` | _boolean (optional)_ | Visibility of the scene item. | **Response Items:** _No additional response items._ --- ### GetTextFreetype2Properties - Added in v4.5.0 Get the current properties of a Text Freetype 2 source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name | | `color1` | _int_ | Gradient top color. | | `color2` | _int_ | Gradient bottom color. | | `custom_width` | _int_ | Custom width (0 to disable). | | `drop_shadow` | _boolean_ | Drop shadow. | | `font` | _Object_ | Holds data for the font. Ex: `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }` | | `font.face` | _String_ | Font face. | | `font.flags` | _int_ | Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8` | | `font.size` | _int_ | Font text size. | | `font.style` | _String_ | Font Style (unknown function). | | `from_file` | _boolean_ | Read text from the specified file. | | `log_mode` | _boolean_ | Chat log. | | `outline` | _boolean_ | Outline. | | `text` | _String_ | Text content to be displayed. | | `text_file` | _String_ | File path. | | `word_wrap` | _boolean_ | Word wrap. | --- ### SetTextFreetype2Properties - Added in v4.5.0 Set the current properties of a Text Freetype 2 source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name. | | `color1` | _int (optional)_ | Gradient top color. | | `color2` | _int (optional)_ | Gradient bottom color. | | `custom_width` | _int (optional)_ | Custom width (0 to disable). | | `drop_shadow` | _boolean (optional)_ | Drop shadow. | | `font` | _Object (optional)_ | Holds data for the font. Ex: `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }` | | `font.face` | _String (optional)_ | Font face. | | `font.flags` | _int (optional)_ | Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8` | | `font.size` | _int (optional)_ | Font text size. | | `font.style` | _String (optional)_ | Font Style (unknown function). | | `from_file` | _boolean (optional)_ | Read text from the specified file. | | `log_mode` | _boolean (optional)_ | Chat log. | | `outline` | _boolean (optional)_ | Outline. | | `text` | _String (optional)_ | Text content to be displayed. | | `text_file` | _String (optional)_ | File path. | | `word_wrap` | _boolean (optional)_ | Word wrap. | **Response Items:** _No additional response items._ --- ### GetBrowserSourceProperties - **⚠️ Deprecated. Since 4.8.0. Prefer the use of GetSourceSettings. Will be removed in v5.0.0 ⚠️** - Added in v4.1.0 Get current properties for a Browser Source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name. | | `is_local_file` | _boolean_ | Indicates that a local file is in use. | | `local_file` | _String_ | file path. | | `url` | _String_ | Url. | | `css` | _String_ | CSS to inject. | | `width` | _int_ | Width. | | `height` | _int_ | Height. | | `fps` | _int_ | Framerate. | | `shutdown` | _boolean_ | Indicates whether the source should be shutdown when not visible. | --- ### SetBrowserSourceProperties - **⚠️ Deprecated. Since 4.8.0. Prefer the use of SetSourceSettings. Will be removed in v5.0.0 ⚠️** - Added in v4.1.0 Set current properties for a Browser Source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Name of the source. | | `is_local_file` | _boolean (optional)_ | Indicates that a local file is in use. | | `local_file` | _String (optional)_ | file path. | | `url` | _String (optional)_ | Url. | | `css` | _String (optional)_ | CSS to inject. | | `width` | _int (optional)_ | Width. | | `height` | _int (optional)_ | Height. | | `fps` | _int (optional)_ | Framerate. | | `shutdown` | _boolean (optional)_ | Indicates whether the source should be shutdown when not visible. | | `render` | _boolean (optional)_ | Visibility of the scene item. | **Response Items:** _No additional response items._ --- ### GetSpecialSources - Added in v4.1.0 Get configured special sources like Desktop Audio and Mic/Aux sources. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `desktop-1` | _String (optional)_ | Name of the first Desktop Audio capture source. | | `desktop-2` | _String (optional)_ | Name of the second Desktop Audio capture source. | | `mic-1` | _String (optional)_ | Name of the first Mic/Aux input source. | | `mic-2` | _String (optional)_ | Name of the second Mic/Aux input source. | | `mic-3` | _String (optional)_ | NAme of the third Mic/Aux input source. | --- ### GetSourceFilters - Added in v4.5.0 List filters applied to a source **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `filters` | _Array<Object>_ | List of filters for the specified source | | `filters.*.enabled` | _Boolean_ | Filter status (enabled or not) | | `filters.*.type` | _String_ | Filter type | | `filters.*.name` | _String_ | Filter name | | `filters.*.settings` | _Object_ | Filter settings | --- ### GetSourceFilterInfo - Added in v4.7.0 List filters applied to a source **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `filterName` | _String_ | Source filter name | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `enabled` | _Boolean_ | Filter status (enabled or not) | | `type` | _String_ | Filter type | | `name` | _String_ | Filter name | | `settings` | _Object_ | Filter settings | --- ### AddFilterToSource - Added in v4.5.0 Add a new filter to a source. Available source types along with their settings properties are available from `GetSourceTypesList`. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Name of the source on which the filter is added | | `filterName` | _String_ | Name of the new filter | | `filterType` | _String_ | Filter type | | `filterSettings` | _Object_ | Filter settings | **Response Items:** _No additional response items._ --- ### RemoveFilterFromSource - Added in v4.5.0 Remove a filter from a source **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Name of the source from which the specified filter is removed | | `filterName` | _String_ | Name of the filter to remove | **Response Items:** _No additional response items._ --- ### ReorderSourceFilter - Added in v4.5.0 Move a filter in the chain (absolute index positioning) **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Name of the source to which the filter belongs | | `filterName` | _String_ | Name of the filter to reorder | | `newIndex` | _Integer_ | Desired position of the filter in the chain | **Response Items:** _No additional response items._ --- ### MoveSourceFilter - Added in v4.5.0 Move a filter in the chain (relative positioning) **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Name of the source to which the filter belongs | | `filterName` | _String_ | Name of the filter to reorder | | `movementType` | _String_ | How to move the filter around in the source's filter chain. Either "up", "down", "top" or "bottom". | **Response Items:** _No additional response items._ --- ### SetSourceFilterSettings - Added in v4.5.0 Update settings of a filter **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Name of the source to which the filter belongs | | `filterName` | _String_ | Name of the filter to reconfigure | | `filterSettings` | _Object_ | New settings. These will be merged to the current filter settings. | **Response Items:** _No additional response items._ --- ### SetSourceFilterVisibility - Added in v4.7.0 Change the visibility/enabled state of a filter **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `filterName` | _String_ | Source filter name | | `filterEnabled` | _Boolean_ | New filter state | **Response Items:** _No additional response items._ --- ### GetAudioMonitorType - Added in v4.8.0 Get the audio monitoring type of the specified source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `monitorType` | _String_ | The monitor type in use. Options: `none`, `monitorOnly`, `monitorAndOutput`. | --- ### SetAudioMonitorType - Added in v4.8.0 Set the audio monitoring type of the specified source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | | `monitorType` | _String_ | The monitor type to use. Options: `none`, `monitorOnly`, `monitorAndOutput`. | **Response Items:** _No additional response items._ --- ### GetSourceDefaultSettings - Added in v4.9.0 Get the default settings for a given source type. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceKind` | _String_ | Source kind. Also called "source id" in libobs terminology. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceKind` | _String_ | Source kind. Same value as the `sourceKind` parameter. | | `defaultSettings` | _Object_ | Settings object for source. | --- ### TakeSourceScreenshot - Added in v4.6.0 At least `embedPictureFormat` or `saveToFilePath` must be specified. Clients can specify `width` and `height` parameters to receive scaled pictures. Aspect ratio is preserved if only one of these two parameters is specified. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String (optional)_ | Source name. Note: Since scenes are also sources, you can also provide a scene name. If not provided, the currently active scene is used. | | `embedPictureFormat` | _String (optional)_ | Format of the Data URI encoded picture. Can be "png", "jpg", "jpeg" or "bmp" (or any other value supported by Qt's Image module) | | `saveToFilePath` | _String (optional)_ | Full file path (file extension included) where the captured image is to be saved. Can be in a format different from `pictureFormat`. Can be a relative path. | | `fileFormat` | _String (optional)_ | Format to save the image file as (one of the values provided in the `supported-image-export-formats` response field of `GetVersion`). If not specified, tries to guess based on file extension. | | `compressionQuality` | _int (optional)_ | Compression ratio between -1 and 100 to write the image with. -1 is automatic, 1 is smallest file/most compression, 100 is largest file/least compression. Varies with image type. | | `width` | _int (optional)_ | Screenshot width. Defaults to the source's base width. | | `height` | _int (optional)_ | Screenshot height. Defaults to the source's base height. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `img` | _String_ | Image Data URI (if `embedPictureFormat` was specified in the request) | | `imageFile` | _String_ | Absolute path to the saved image file (if `saveToFilePath` was specified in the request) | --- ### RefreshBrowserSource - Added in v4.9.0 Refreshes the specified browser source. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sourceName` | _String_ | Source name. | **Response Items:** _No additional response items._ --- ## Outputs ### ListOutputs - Added in v4.7.0 List existing outputs **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `outputs` | _Array<Output>_ | Outputs list | --- ### GetOutputInfo - Added in v4.7.0 Get information about a single output **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `outputName` | _String_ | Output name | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `outputInfo` | _Output_ | Output info | --- ### StartOutput - Added in v4.7.0 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. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `outputName` | _String_ | Output name | **Response Items:** _No additional response items._ --- ### StopOutput - Added in v4.7.0 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. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `outputName` | _String_ | Output name | | `force` | _boolean (optional)_ | Force stop (default: false) | **Response Items:** _No additional response items._ --- ## Profiles ### SetCurrentProfile - Added in v4.0.0 Set the currently active profile. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `profile-name` | _String_ | Name of the desired profile. | **Response Items:** _No additional response items._ --- ### GetCurrentProfile - Added in v4.0.0 Get the name of the current profile. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `profile-name` | _String_ | Name of the currently active profile. | --- ### ListProfiles - Added in v4.0.0 Get a list of available profiles. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `profiles` | _Array<Object>_ | List of available profiles. | | `profiles.*.profile-name` | _String_ | Filter name | --- ## Recording ### GetRecordingStatus - Added in v4.9.0 Get current recording status. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `isRecording` | _boolean_ | Current recording status. | | `isRecordingPaused` | _boolean_ | Whether the recording is paused or not. | | `recordTimecode` | _String (optional)_ | Time elapsed since recording started (only present if currently recording). | | `recordingFilename` | _String (optional)_ | Absolute path to the recording file (only present if currently recording). | --- ### StartStopRecording - Added in v0.3 Toggle recording on or off (depending on the current recording state). **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ### StartRecording - Added in v4.1.0 Start recording. Will return an `error` if recording is already active. **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ### StopRecording - Added in v4.1.0 Stop recording. Will return an `error` if recording is not active. **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ### PauseRecording - Added in v4.7.0 Pause the current recording. Returns an error if recording is not active or already paused. **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ### ResumeRecording - Added in v4.7.0 Resume/unpause the current recording (if paused). Returns an error if recording is not active or not paused. **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ### SetRecordingFolder - Added in v4.1.0 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. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `rec-folder` | _String_ | Path of the recording folder. | **Response Items:** _No additional response items._ --- ### GetRecordingFolder - Added in v4.1.0 Get the path of the current recording folder. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `rec-folder` | _String_ | Path of the recording folder. | --- ## Replay Buffer ### GetReplayBufferStatus - Added in v4.9.0 Get the status of the OBS replay buffer. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `isReplayBufferActive` | _boolean_ | Current recording status. | --- ### StartStopReplayBuffer - Added in v4.2.0 Toggle the Replay Buffer on/off (depending on the current state of the replay buffer). **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ### StartReplayBuffer - Added in v4.2.0 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. **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ### StopReplayBuffer - Added in v4.2.0 Stop recording into the Replay Buffer. Will return an `error` if the Replay Buffer is not active. **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ### SaveReplayBuffer - Added in v4.2.0 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. **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ## Scene Collections ### SetCurrentSceneCollection - Added in v4.0.0 Change the active scene collection. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sc-name` | _String_ | Name of the desired scene collection. | **Response Items:** _No additional response items._ --- ### GetCurrentSceneCollection - Added in v4.0.0 Get the name of the current scene collection. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sc-name` | _String_ | Name of the currently active scene collection. | --- ### ListSceneCollections - Added in v4.0.0 List available scene collections **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-collections` | _Array<ScenesCollection>_ | Scene collections list | --- ## Scene Items ### GetSceneItemList - Added in v4.9.0 Get a list of all scene items in a scene. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sceneName` | _String (optional)_ | Name of the scene to get the list of scene items from. Defaults to the current scene if not specified. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `sceneName` | _String_ | Name of the requested (or current) scene | | `sceneItems` | _Array<Object>_ | Array of scene items | | `sceneItems.*.itemId` | _int_ | Unique item id of the source item | | `sceneItems.*.sourceKind` | _String_ | ID if the scene item's source. For example `vlc_source` or `image_source` | | `sceneItems.*.sourceName` | _String_ | Name of the scene item's source | | `sceneItems.*.sourceType` | _String_ | Type of the scene item's source. Either `input`, `group`, or `scene` | --- ### GetSceneItemProperties - Added in v4.3.0 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). **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String (optional)_ | Name of the scene the scene item belongs to. Defaults to the current scene. | | `item` | _String \| Object_ | Scene Item name (if this field is a string) or specification (if it is an object). | | `item.name` | _String (optional)_ | Scene Item name (if the `item` field is an object) | | `item.id` | _int (optional)_ | Scene Item ID (if the `item` field is an object) | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `name` | _String_ | Scene Item name. | | `itemId` | _int_ | Scene Item ID. | | `position.x` | _double_ | The x position of the source from the left. | | `position.y` | _double_ | The y position of the source from the top. | | `position.alignment` | _int_ | 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. | | `rotation` | _double_ | The clockwise rotation of the item in degrees around the point of alignment. | | `scale.x` | _double_ | The x-scale factor of the source. | | `scale.y` | _double_ | The y-scale factor of the source. | | `crop.top` | _int_ | The number of pixels cropped off the top of the source before scaling. | | `crop.right` | _int_ | The number of pixels cropped off the right of the source before scaling. | | `crop.bottom` | _int_ | The number of pixels cropped off the bottom of the source before scaling. | | `crop.left` | _int_ | The number of pixels cropped off the left of the source before scaling. | | `visible` | _bool_ | If the source is visible. | | `muted` | _bool_ | If the source is muted. | | `locked` | _bool_ | If the source's transform is locked. | | `bounds.type` | _String_ | 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". | | `bounds.alignment` | _int_ | Alignment of the bounding box. | | `bounds.x` | _double_ | Width of the bounding box. | | `bounds.y` | _double_ | Height of the bounding box. | | `sourceWidth` | _int_ | Base width (without scaling) of the source | | `sourceHeight` | _int_ | Base source (without scaling) of the source | | `width` | _double_ | Scene item width (base source width multiplied by the horizontal scaling factor) | | `height` | _double_ | Scene item height (base source height multiplied by the vertical scaling factor) | | `parentGroupName` | _String (optional)_ | Name of the item's parent (if this item belongs to a group) | | `groupChildren` | _Array<SceneItemTransform> (optional)_ | List of children (if this item is a group) | --- ### SetSceneItemProperties - Added in v4.3.0 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). **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String (optional)_ | Name of the scene the source item belongs to. Defaults to the current scene. | | `item` | _String \| Object_ | Scene Item name (if this field is a string) or specification (if it is an object). | | `item.name` | _String (optional)_ | Scene Item name (if the `item` field is an object) | | `item.id` | _int (optional)_ | Scene Item ID (if the `item` field is an object) | | `position.x` | _double (optional)_ | The new x position of the source. | | `position.y` | _double (optional)_ | The new y position of the source. | | `position.alignment` | _int (optional)_ | The new alignment of the source. | | `rotation` | _double (optional)_ | The new clockwise rotation of the item in degrees. | | `scale.x` | _double (optional)_ | The new x scale of the item. | | `scale.y` | _double (optional)_ | The new y scale of the item. | | `crop.top` | _int (optional)_ | The new amount of pixels cropped off the top of the source before scaling. | | `crop.bottom` | _int (optional)_ | The new amount of pixels cropped off the bottom of the source before scaling. | | `crop.left` | _int (optional)_ | The new amount of pixels cropped off the left of the source before scaling. | | `crop.right` | _int (optional)_ | The new amount of pixels cropped off the right of the source before scaling. | | `visible` | _bool (optional)_ | The new visibility of the source. 'true' shows source, 'false' hides source. | | `locked` | _bool (optional)_ | The new locked status of the source. 'true' keeps it in its current position, 'false' allows movement. | | `bounds.type` | _String (optional)_ | 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". | | `bounds.alignment` | _int (optional)_ | The new alignment of the bounding box. (0-2, 4-6, 8-10) | | `bounds.x` | _double (optional)_ | The new width of the bounding box. | | `bounds.y` | _double (optional)_ | The new height of the bounding box. | **Response Items:** _No additional response items._ --- ### ResetSceneItem - Added in v4.2.0 Reset a scene item. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String (optional)_ | Name of the scene the scene item belongs to. Defaults to the current scene. | | `item` | _String \| Object_ | Scene Item name (if this field is a string) or specification (if it is an object). | | `item.name` | _String (optional)_ | Scene Item name (if the `item` field is an object) | | `item.id` | _int (optional)_ | Scene Item ID (if the `item` field is an object) | **Response Items:** _No additional response items._ --- ### SetSceneItemRender - Added in v0.3 Show or hide a specified source item in a specified scene. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String (optional)_ | Name of the scene the scene item belongs to. Defaults to the currently active scene. | | `source` | _String (optional)_ | Scene Item name. | | `item` | _int (optional)_ | Scene Item id | | `render` | _boolean_ | true = shown ; false = hidden | **Response Items:** _No additional response items._ --- ### SetSceneItemPosition - **⚠️ Deprecated. Since 4.3.0. Prefer the use of SetSceneItemProperties. ⚠️** - Added in v4.0.0 Sets the coordinates of a specified source item. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String (optional)_ | Name of the scene the scene item belongs to. Defaults to the current scene. | | `item` | _String_ | Scene Item name. | | `x` | _double_ | X coordinate. | | `y` | _double_ | Y coordinate. | **Response Items:** _No additional response items._ --- ### SetSceneItemTransform - **⚠️ Deprecated. Since 4.3.0. Prefer the use of SetSceneItemProperties. ⚠️** - Added in v4.0.0 Set the transform of the specified source item. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String (optional)_ | Name of the scene the scene item belongs to. Defaults to the current scene. | | `item` | _String_ | Scene Item name. | | `x-scale` | _double_ | Width scale factor. | | `y-scale` | _double_ | Height scale factor. | | `rotation` | _double_ | Source item rotation (in degrees). | **Response Items:** _No additional response items._ --- ### SetSceneItemCrop - **⚠️ Deprecated. Since 4.3.0. Prefer the use of SetSceneItemProperties. ⚠️** - Added in v4.1.0 Sets the crop coordinates of the specified source item. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String (optional)_ | Name of the scene the scene item belongs to. Defaults to the current scene. | | `item` | _String_ | Scene Item name. | | `top` | _int_ | Pixel position of the top of the source item. | | `bottom` | _int_ | Pixel position of the bottom of the source item. | | `left` | _int_ | Pixel position of the left of the source item. | | `right` | _int_ | Pixel position of the right of the source item. | **Response Items:** _No additional response items._ --- ### DeleteSceneItem - Added in v4.5.0 Deletes a scene item. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `scene` | _String (optional)_ | Name of the scene the scene item belongs to. Defaults to the current scene. | | `item` | _Object_ | Scene item to delete (required) | | `item.name` | _String_ | Scene Item name (prefer `id`, including both is acceptable). | | `item.id` | _int_ | Scene Item ID. | **Response Items:** _No additional response items._ --- ### AddSceneItem - Added in v4.9.0 Creates a scene item in a scene. In other words, this is how you add a source into a scene. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sceneName` | _String_ | Name of the scene to create the scene item in | | `sourceName` | _String_ | Name of the source to be added | | `setVisible` | _boolean_ | Whether to make the sceneitem visible on creation or not. Default `true` | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `itemId` | _int_ | Numerical ID of the created scene item | --- ### DuplicateSceneItem - Added in v4.5.0 Duplicates a scene item. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `fromScene` | _String (optional)_ | Name of the scene to copy the item from. Defaults to the current scene. | | `toScene` | _String (optional)_ | Name of the scene to create the item in. Defaults to the current scene. | | `item` | _Object_ | Scene Item to duplicate from the source scene (required) | | `item.name` | _String_ | Scene Item name (prefer `id`, including both is acceptable). | | `item.id` | _int_ | Scene Item ID. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `scene` | _String_ | Name of the scene where the new item was created | | `item` | _Object_ | New item info | | `item.id` | _int_ | New item ID | | `item.name` | _String_ | New item name | --- ## Scenes ### SetCurrentScene - Added in v0.3 Switch to the specified scene. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String_ | Name of the scene to switch to. | **Response Items:** _No additional response items._ --- ### GetCurrentScene - Added in v0.3 Get the current scene's name and source items. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `name` | _String_ | Name of the currently active scene. | | `sources` | _Array<SceneItem>_ | Ordered list of the current scene's source items. | --- ### GetSceneList - Added in v0.3 Get a list of scenes in the currently active profile. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `current-scene` | _String_ | Name of the currently active scene. | | `scenes` | _Array<Scene>_ | Ordered list of the current profile's scenes (See [GetCurrentScene](#getcurrentscene) for more information). | --- ### CreateScene - Added in v4.9.0 Create a new scene scene. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sceneName` | _String_ | Name of the scene to create. | **Response Items:** _No additional response items._ --- ### ReorderSceneItems - Added in v4.5.0 Changes the order of scene items in the requested scene. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `scene` | _String (optional)_ | Name of the scene to reorder (defaults to current). | | `items` | _Array<Scene>_ | Ordered list of objects with name and/or id specified. Id preferred due to uniqueness per scene | | `items.*.id` | _int (optional)_ | Id of a specific scene item. Unique on a scene by scene basis. | | `items.*.name` | _String (optional)_ | Name of a scene item. Sufficiently unique if no scene items share sources within the scene. | **Response Items:** _No additional response items._ --- ### SetSceneTransitionOverride - Added in v4.8.0 Set a scene to use a specific transition override. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sceneName` | _String_ | Name of the scene to switch to. | | `transitionName` | _String_ | Name of the transition to use. | | `transitionDuration` | _int (Optional)_ | Duration in milliseconds of the transition if transition is not fixed. Defaults to the current duration specified in the UI if there is no current override and this value is not given. | **Response Items:** _No additional response items._ --- ### RemoveSceneTransitionOverride - Added in v4.8.0 Remove any transition override on a scene. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sceneName` | _String_ | Name of the scene to switch to. | **Response Items:** _No additional response items._ --- ### GetSceneTransitionOverride - Added in v4.8.0 Get the current scene transition override. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `sceneName` | _String_ | Name of the scene to switch to. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `transitionName` | _String_ | Name of the current overriding transition. Empty string if no override is set. | | `transitionDuration` | _int_ | Transition duration. `-1` if no override is set. | --- ## Streaming ### GetStreamingStatus - Added in v0.3 Get current streaming and recording status. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `streaming` | _boolean_ | Current streaming status. | | `recording` | _boolean_ | Current recording status. | | `recording-paused` | _boolean_ | If recording is paused. | | `preview-only` | _boolean_ | Always false. Retrocompatibility with OBSRemote. | | `stream-timecode` | _String (optional)_ | Time elapsed since streaming started (only present if currently streaming). | | `rec-timecode` | _String (optional)_ | Time elapsed since recording started (only present if currently recording). | --- ### StartStopStreaming - Added in v0.3 Toggle streaming on or off (depending on the current stream state). **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ### StartStreaming - Added in v4.1.0 Start streaming. Will return an `error` if streaming is already active. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `stream` | _Object (optional)_ | Special stream configuration. Note: these won't be saved to OBS' configuration. | | `stream.type` | _String (optional)_ | If specified ensures the type of stream matches the given type (usually 'rtmp_custom' or 'rtmp_common'). If the currently configured stream type does not match the given stream type, all settings must be specified in the `settings` object or an error will occur when starting the stream. | | `stream.metadata` | _Object (optional)_ | Adds the given object parameters as encoded query string parameters to the 'key' of the RTMP stream. Used to pass data to the RTMP service about the streaming. May be any String, Numeric, or Boolean field. | | `stream.settings` | _Object (optional)_ | Settings for the stream. | | `stream.settings.server` | _String (optional)_ | The publish URL. | | `stream.settings.key` | _String (optional)_ | The publish key of the stream. | | `stream.settings.use_auth` | _boolean (optional)_ | Indicates whether authentication should be used when connecting to the streaming server. | | `stream.settings.username` | _String (optional)_ | If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`. | | `stream.settings.password` | _String (optional)_ | If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`. | **Response Items:** _No additional response items._ --- ### StopStreaming - Added in v4.1.0 Stop streaming. Will return an `error` if streaming is not active. **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ### SetStreamSettings - Added in v4.1.0 Sets one or more attributes of the current streaming server settings. Any options not passed will remain unchanged. Returns the updated settings in response. If 'type' is different than the current streaming service type, all settings are required. Returns the full settings of the stream (the same as GetStreamSettings). **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `type` | _String_ | The type of streaming service configuration, usually `rtmp_custom` or `rtmp_common`. | | `settings` | _Object_ | The actual settings of the stream. | | `settings.server` | _String (optional)_ | The publish URL. | | `settings.key` | _String (optional)_ | The publish key. | | `settings.use_auth` | _boolean (optional)_ | Indicates whether authentication should be used when connecting to the streaming server. | | `settings.username` | _String (optional)_ | The username for the streaming service. | | `settings.password` | _String (optional)_ | The password for the streaming service. | | `save` | _boolean_ | Persist the settings to disk. | **Response Items:** _No additional response items._ --- ### GetStreamSettings - Added in v4.1.0 Get the current streaming server settings. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `type` | _String_ | The type of streaming service configuration. Possible values: 'rtmp_custom' or 'rtmp_common'. | | `settings` | _Object_ | Stream settings object. | | `settings.server` | _String_ | The publish URL. | | `settings.key` | _String_ | The publish key of the stream. | | `settings.use_auth` | _boolean_ | Indicates whether authentication should be used when connecting to the streaming server. | | `settings.username` | _String_ | The username to use when accessing the streaming server. Only present if `use_auth` is `true`. | | `settings.password` | _String_ | The password to use when accessing the streaming server. Only present if `use_auth` is `true`. | --- ### SaveStreamSettings - Added in v4.1.0 Save the current streaming server settings to disk. **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ### SendCaptions - Added in v4.6.0 Send the provided text as embedded CEA-608 caption data. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `text` | _String_ | Captions text | **Response Items:** _No additional response items._ --- ## Studio Mode ### GetStudioModeStatus - Added in v4.1.0 Indicates if Studio Mode is currently enabled. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `studio-mode` | _boolean_ | Indicates if Studio Mode is enabled. | --- ### GetPreviewScene - Added in v4.1.0 Get the name of the currently previewed scene and its list of sources. Will return an `error` if Studio Mode is not enabled. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `name` | _String_ | The name of the active preview scene. | | `sources` | _Array<SceneItem>_ | | --- ### SetPreviewScene - Added in v4.1.0 Set the active preview scene. Will return an `error` if Studio Mode is not enabled. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `scene-name` | _String_ | The name of the scene to preview. | **Response Items:** _No additional response items._ --- ### TransitionToProgram - Added in v4.1.0 Transitions the currently previewed scene to the main output. Will return an `error` if Studio Mode is not enabled. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `with-transition` | _Object (optional)_ | Change the active transition before switching scenes. Defaults to the active transition. | | `with-transition.name` | _String_ | Name of the transition. | | `with-transition.duration` | _int (optional)_ | Transition duration (in milliseconds). | **Response Items:** _No additional response items._ --- ### EnableStudioMode - Added in v4.1.0 Enables Studio Mode. **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ### DisableStudioMode - Added in v4.1.0 Disables Studio Mode. **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ### ToggleStudioMode - Added in v4.1.0 Toggles Studio Mode (depending on the current state of studio mode). **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ## Transitions ### GetTransitionList - Added in v4.1.0 List of all transitions available in the frontend's dropdown menu. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `current-transition` | _String_ | Name of the currently active transition. | | `transitions` | _Array<Object>_ | List of transitions. | | `transitions.*.name` | _String_ | Name of the transition. | --- ### GetCurrentTransition - Added in v0.3 Get the name of the currently selected transition in the frontend's dropdown menu. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `name` | _String_ | Name of the selected transition. | | `duration` | _int (optional)_ | Transition duration (in milliseconds) if supported by the transition. | --- ### SetCurrentTransition - Added in v0.3 Set the active transition. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `transition-name` | _String_ | The name of the transition. | **Response Items:** _No additional response items._ --- ### SetTransitionDuration - Added in v4.0.0 Set the duration of the currently selected transition if supported. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `duration` | _int_ | Desired duration of the transition (in milliseconds). | **Response Items:** _No additional response items._ --- ### GetTransitionDuration - Added in v4.1.0 Get the duration of the currently selected transition if supported. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `transition-duration` | _int_ | Duration of the current transition (in milliseconds). | --- ### GetTransitionPosition - Added in v4.9.0 Get the position of the current transition. **Request Fields:** _No specified parameters._ **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `position` | _double_ | current transition position. This value will be between 0.0 and 1.0. Note: Transition returns 1.0 when not active. | --- ### GetTransitionSettings - Added in v4.9.0 Get the current settings of a transition **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `transitionName` | _String_ | Transition name | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `transitionSettings` | _Object_ | Current transition settings | --- ### SetTransitionSettings - Added in v4.9.0 Change the current settings of a transition **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `transitionName` | _String_ | Transition name | | `transitionSettings` | _Object_ | Transition settings (they can be partial) | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| | `transitionSettings` | _Object_ | Updated transition settings | --- ### ReleaseTBar - Added in v4.9.0 Release the T-Bar (like a user releasing their mouse button after moving it). *YOU MUST CALL THIS if you called `SetTBarPosition` with the `release` parameter set to `false`.* **Request Fields:** _No specified parameters._ **Response Items:** _No additional response items._ --- ### SetTBarPosition - Added in v4.9.0 If your code needs to perform multiple successive T-Bar moves (e.g. : in an animation, or in response to a user moving a T-Bar control in your User Interface), set `release` to false and call `ReleaseTBar` later once the animation/interaction is over. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `position` | _double_ | T-Bar position. This value must be between 0.0 and 1.0. | | `release` | _boolean (optional)_ | Whether or not the T-Bar gets released automatically after setting its new position (like a user releasing their mouse button after moving the T-Bar). Call `ReleaseTBar` manually if you set `release` to false. Defaults to true. | **Response Items:** _No additional response items._ --- obs-websocket-4.9.0/docs/package.json000066400000000000000000000007671401107467600175310ustar00rootroot00000000000000{ "name": "obs-websocket-docs", "version": "1.0.0", "description": "", "main": "docs.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "docs": "node ./docs.js", "comments": "node ./comments.js", "build": "npm run comments && npm run docs" }, "author": "", "license": "ISC", "dependencies": { "glob": "^7.1.2", "handlebars": "^4.0.10", "handlebars-helpers": "^0.9.6", "markdown-toc": "^1.1.0", "parse-comments": "^0.4.3" } } obs-websocket-4.9.0/docs/partials/000077500000000000000000000000001401107467600170505ustar00rootroot00000000000000obs-websocket-4.9.0/docs/partials/eventsHeader.md000066400000000000000000000011461401107467600220110ustar00rootroot00000000000000# Events Events are broadcast by the server to each connected client when a recognized action occurs within OBS. An event message will contain at least the following base fields: - `update-type` _String_: the type of event. - `stream-timecode` _String (optional)_: time elapsed between now and stream start (only present if OBS Studio is streaming). - `rec-timecode` _String (optional)_: time elapsed between now and recording start (only present if OBS Studio is recording). Timecodes are sent using the format: `HH:MM:SS.mmm` Additional fields may be present in the event message depending on the event type. obs-websocket-4.9.0/docs/partials/introduction.md000066400000000000000000000036601401107467600221200ustar00rootroot00000000000000# 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. As of v5.0.0, backwards compatability with the protocol will not be kept. # Authentication **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. - A `salt`: applied to the password when generating the auth response. To generate the answer to the auth challenge, follow this procedure: - Concatenate the user declared password with the `salt` sent by the server (in this order: `password + server salt`). - Generate a binary SHA256 hash of the result and encode the resulting SHA256 binary hash to base64, known as a `base64 secret`. - Concatenate the base64 secret with the `challenge` sent by the server (in this order: `base64 secret + server challenge`). - Generate a binary SHA256 hash of the result and encode it to base64. - Voilà, this last base64 string is the `auth response`. You may now use it to authenticate to the server with the [`Authenticate`](#authenticate) request. Pseudo Code Example: ``` password = "supersecretpassword" challenge = "ztTBnnuqrqaKDzRM3xcVdbYm" salt = "PZVbYpvAnZut2SS6JNJytDm9" secret_string = password + salt secret_hash = binary_sha256(secret_string) secret = base64_encode(secret_hash) 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. obs-websocket-4.9.0/docs/partials/requestsHeader.md000066400000000000000000000013011401107467600223510ustar00rootroot00000000000000# Requests Requests are sent by the client and require at least the following two fields: - `request-type` _String_: String name of the request type. - `message-id` _String_: Client defined identifier for the message, will be echoed in the response. 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 (Optional)_: An error message accompanying an `error` status. Additional information may be required/returned depending on the request type. See below for more information. obs-websocket-4.9.0/docs/partials/typedefsHeader.md000066400000000000000000000002241401107467600223240ustar00rootroot00000000000000# Typedefs These are complex types, such as `Source` and `Scene`, which are used as arguments or return values in multiple requests and/or events. obs-websocket-4.9.0/docs/protocol.hbs000066400000000000000000000032731401107467600175750ustar00rootroot00000000000000{{#read "partials/introduction.md"}}{{/read}} # Table of Contents {{#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}} ## {{capitalizeAll @key}} {{#each this}} ### {{name}} {{#if deprecated}} - **⚠️ Deprecated. {{deprecated}} ⚠️** {{/if}} {{#eq since "unreleased"}} - Unreleased {{else}} - Added in v{{since}} {{/eq}} {{{description}}} **Response Items:** {{#if returns.length}} | Name | Type | Description | | ---- | :---: | ------------| {{#each returns}} | `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} | {{/each}} {{else}} _No additional response items._ {{/if}} --- {{/each}} {{/each}} {{#read "partials/requestsHeader.md"}}{{/read}} {{#each requests}} ## {{capitalizeAll @key}} {{#each this}} ### {{name}} {{#if deprecated}} - **⚠️ Deprecated. {{deprecated}} ⚠️** {{/if}} {{#eq since "unreleased"}} - Unreleased {{else}} - Added in v{{since}} {{/eq}} {{{description}}} **Request Fields:** {{#if params.length}} | Name | Type | Description | | ---- | :---: | ------------| {{#each params}} | `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} | {{/each}} {{else}} _No specified parameters._ {{/if}} **Response Items:** {{#if returns.length}} | Name | Type | Description | | ---- | :---: | ------------| {{#each returns}} | `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} | {{/each}} {{else}} _No additional response items._ {{/if}} --- {{/each}} {{/each}} obs-websocket-4.9.0/external/000077500000000000000000000000001401107467600161235ustar00rootroot00000000000000obs-websocket-4.9.0/external/FindLibObs.cmake000066400000000000000000000057471401107467600211150ustar00rootroot00000000000000# This module can be copied and used by external plugins for OBS # # Once done these will be defined: # # LIBOBS_FOUND # LIBOBS_INCLUDE_DIRS # LIBOBS_LIBRARIES find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) pkg_check_modules(_OBS QUIET obs libobs) endif() if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(_lib_suffix 64) else() set(_lib_suffix 32) endif() if(DEFINED CMAKE_BUILD_TYPE) if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(_build_type_base "debug") else() set(_build_type_base "release") endif() endif() find_path(LIBOBS_INCLUDE_DIR NAMES obs.h HINTS ENV obsPath${_lib_suffix} ENV obsPath ${obsPath} PATHS /usr/include /usr/local/include /opt/local/include /sw/include PATH_SUFFIXES libobs ) function(find_obs_lib base_name repo_build_path lib_name) string(TOUPPER "${base_name}" base_name_u) if(DEFINED _build_type_base) set(_build_type_${repo_build_path} "${_build_type_base}/${repo_build_path}") set(_build_type_${repo_build_path}${_lib_suffix} "${_build_type_base}${_lib_suffix}/${repo_build_path}") endif() find_library(${base_name_u}_LIB NAMES ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name} HINTS ENV obsPath${_lib_suffix} ENV obsPath ${obsPath} ${_${base_name_u}_LIBRARY_DIRS} PATHS /usr/lib /usr/local/lib /opt/local/lib /sw/lib PATH_SUFFIXES lib${_lib_suffix} lib libs${_lib_suffix} libs bin${_lib_suffix} bin ../lib${_lib_suffix} ../lib ../libs${_lib_suffix} ../libs ../bin${_lib_suffix} ../bin # base repo non-msvc-specific search paths ${_build_type_${repo_build_path}} ${_build_type_${repo_build_path}${_lib_suffix}} build/${repo_build_path} build${_lib_suffix}/${repo_build_path} # base repo msvc-specific search paths on windows build${_lib_suffix}/${repo_build_path}/Debug build${_lib_suffix}/${repo_build_path}/RelWithDebInfo build/${repo_build_path}/Debug build/${repo_build_path}/RelWithDebInfo ) endfunction() find_obs_lib(LIBOBS libobs obs) if(MSVC) find_obs_lib(W32_PTHREADS deps/w32-pthreads w32-pthreads) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Libobs DEFAULT_MSG LIBOBS_LIB LIBOBS_INCLUDE_DIR) mark_as_advanced(LIBOBS_INCLUDE_DIR LIBOBS_LIB) if(LIBOBS_FOUND) if(MSVC) if (NOT DEFINED W32_PTHREADS_LIB) message(FATAL_ERROR "Could not find the w32-pthreads library" ) endif() set(W32_PTHREADS_INCLUDE_DIR ${LIBOBS_INCLUDE_DIR}/../deps/w32-pthreads) endif() set(LIBOBS_INCLUDE_DIRS ${LIBOBS_INCLUDE_DIR} ${W32_PTHREADS_INCLUDE_DIR}) set(LIBOBS_LIBRARIES ${LIBOBS_LIB} ${W32_PTHREADS_LIB}) include(${LIBOBS_INCLUDE_DIR}/../cmake/external/ObsPluginHelpers.cmake) # allows external plugins to easily use/share common dependencies that are often included with libobs (such as FFmpeg) if(NOT DEFINED INCLUDED_LIBOBS_CMAKE_MODULES) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${LIBOBS_INCLUDE_DIR}/../cmake/Modules/") set(INCLUDED_LIBOBS_CMAKE_MODULES true) endif() else() message(FATAL_ERROR "Could not find the libobs library" ) endif() obs-websocket-4.9.0/installer/000077500000000000000000000000001401107467600162765ustar00rootroot00000000000000obs-websocket-4.9.0/installer/installer.iss000066400000000000000000000046331401107467600210210ustar00rootroot00000000000000; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "obs-websocket" #define MyAppVersion "4.9.0" #define MyAppPublisher "Stephane Lepin" #define MyAppURL "http://github.com/Palakis/obs-websocket" [Setup] ; NOTE: The value of AppId uniquely identifies this application. ; Do not use the same AppId value in installers for other applications. ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) AppId={{117EE44F-48E1-49E5-A381-CC8D9195CF35} AppName={#MyAppName} AppVersion={#MyAppVersion} ;AppVerName={#MyAppName} {#MyAppVersion} AppPublisher={#MyAppPublisher} AppPublisherURL={#MyAppURL} AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} DefaultDirName={code:GetDirName} DefaultGroupName={#MyAppName} OutputBaseFilename=obs-websocket-Windows-Installer Compression=lzma SolidCompression=yes DirExistsWarning=no [Languages] Name: "english"; MessagesFile: "compiler:Default.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] 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; 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 end; obs-websocket-4.9.0/src/000077500000000000000000000000001401107467600150705ustar00rootroot00000000000000obs-websocket-4.9.0/src/Config.cpp000066400000000000000000000251001401107467600167770ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2017 Stéphane Lepin 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 */ #include #include #include #include #include #include #include #include #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(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( 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); } } obs-websocket-4.9.0/src/Config.h000066400000000000000000000027621401107467600164550ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2019 Stéphane Lepin 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 */ #pragma once #include #include #include #include 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(); }; obs-websocket-4.9.0/src/ConnectionProperties.cpp000066400000000000000000000017441401107467600217560ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2019 Stéphane Lepin 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 */ #include "ConnectionProperties.h" ConnectionProperties::ConnectionProperties() : _authenticated(false) { } bool ConnectionProperties::isAuthenticated() { return _authenticated.load(); } void ConnectionProperties::setAuthenticated(bool authenticated) { _authenticated.store(authenticated); }obs-websocket-4.9.0/src/ConnectionProperties.h000066400000000000000000000016271401107467600214230ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2019 Stéphane Lepin 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 */ #pragma once #include class ConnectionProperties { public: explicit ConnectionProperties(); bool isAuthenticated(); void setAuthenticated(bool authenticated); private: std::atomic _authenticated; };obs-websocket-4.9.0/src/Utils.cpp000066400000000000000000000715111401107467600167010ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2017 Stéphane Lepin 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 */ #include #include #include #include #include #include #include #include #include "obs-websocket.h" #include "Utils.h" #include "Config.h" Q_DECLARE_METATYPE(OBSScene); const QHash 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(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 (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(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(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(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("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(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(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 (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(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(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(_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; } obs-websocket-4.9.0/src/Utils.h000066400000000000000000000064011401107467600163420ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2019 Stéphane Lepin 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 */ #pragma once #include #include #include #include #include #include #include #include #include #include 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); }; obs-websocket-4.9.0/src/WSEvents.cpp000066400000000000000000001756071401107467600173320ustar00rootroot00000000000000/** * obs-websocket * Copyright (C) 2016-2017 Stéphane Lepin * Copyright (C) 2017 Brendan Hagan * * 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 */ #include #include #include #include #include "WSEvents.h" #include "obs-websocket.h" #include "Config.h" #include "Utils.h" #include "rpc/RpcEvent.h" #define STATUS_INTERVAL 2000 const char* sourceTypeToString(obs_source_type type) { switch (type) { case OBS_SOURCE_TYPE_INPUT: return "input"; case OBS_SOURCE_TYPE_SCENE: return "scene"; case OBS_SOURCE_TYPE_TRANSITION: return "transition"; case OBS_SOURCE_TYPE_FILTER: return "filter"; default: return "unknown"; } } template T* calldata_get_pointer(const calldata_t* data, const char* name) { void* ptr = nullptr; calldata_get_ptr(data, name, &ptr); return reinterpret_cast(ptr); } const char* calldata_get_string(const calldata_t* data, const char* name) { const char* value = nullptr; calldata_get_string(data, name, &value); return value; } WSEvents::WSEvents(WSServerPtr srv) : _srv(srv), _streamStarttime(0), _lastBytesSent(0), _lastBytesSentTime(0), HeartbeatIsActive(false), pulse(false) { cpuUsageInfo = os_cpu_usage_info_start(); obs_frontend_add_event_callback(WSEvents::FrontendEventHandler, this); QSpinBox* durationControl = Utils::GetTransitionDurationControl(); connect(durationControl, SIGNAL(valueChanged(int)), this, SLOT(TransitionDurationChanged(int))); connect(&streamStatusTimer, SIGNAL(timeout()), this, SLOT(StreamStatus())); connect(&heartbeatTimer, SIGNAL(timeout()), this, SLOT(Heartbeat())); heartbeatTimer.start(STATUS_INTERVAL); // Connect to signals of all existing sources obs_enum_sources([](void* param, obs_source_t* source) { auto self = reinterpret_cast(param); self->connectSourceSignals(source); return true; }, this); signal_handler_t* coreSignalHandler = obs_get_signal_handler(); if (coreSignalHandler) { signal_handler_connect(coreSignalHandler, "source_create", OnSourceCreate, this); signal_handler_connect(coreSignalHandler, "source_destroy", OnSourceDestroy, this); } } WSEvents::~WSEvents() { signal_handler_t* coreSignalHandler = obs_get_signal_handler(); if (coreSignalHandler) { signal_handler_disconnect(coreSignalHandler, "source_destroy", OnSourceDestroy, this); signal_handler_disconnect(coreSignalHandler, "source_create", OnSourceCreate, this); } // Disconnect from signals of all existing sources obs_enum_sources([](void* param, obs_source_t* source) { auto self = reinterpret_cast(param); self->disconnectSourceSignals(source); return true; }, this); obs_frontend_remove_event_callback(WSEvents::FrontendEventHandler, this); os_cpu_usage_info_destroy(cpuUsageInfo); } void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private_data) { auto owner = reinterpret_cast(private_data); if (!owner->_srv) { return; } switch (event) { case OBS_FRONTEND_EVENT_FINISHED_LOADING: owner->hookTransitionPlaybackEvents(); break; case OBS_FRONTEND_EVENT_SCENE_CHANGED: owner->OnSceneChange(); break; case OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED: owner->OnSceneListChange(); break; case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED: owner->hookTransitionPlaybackEvents(); owner->OnSceneCollectionChange(); break; case OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED: owner->OnSceneCollectionListChange(); break; case OBS_FRONTEND_EVENT_TRANSITION_CHANGED: owner->OnTransitionChange(); break; case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED: owner->hookTransitionPlaybackEvents(); owner->OnTransitionListChange(); break; case OBS_FRONTEND_EVENT_PROFILE_CHANGED: owner->OnProfileChange(); break; case OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED: owner->OnProfileListChange(); break; case OBS_FRONTEND_EVENT_STREAMING_STARTING: owner->OnStreamStarting(); break; case OBS_FRONTEND_EVENT_STREAMING_STARTED: owner->streamStatusTimer.start(STATUS_INTERVAL); owner->StreamStatus(); owner->OnStreamStarted(); break; case OBS_FRONTEND_EVENT_STREAMING_STOPPING: owner->streamStatusTimer.stop(); owner->OnStreamStopping(); break; case OBS_FRONTEND_EVENT_STREAMING_STOPPED: owner->OnStreamStopped(); break; case OBS_FRONTEND_EVENT_RECORDING_STARTING: owner->OnRecordingStarting(); break; case OBS_FRONTEND_EVENT_RECORDING_STARTED: owner->OnRecordingStarted(); break; case OBS_FRONTEND_EVENT_RECORDING_STOPPING: owner->OnRecordingStopping(); break; case OBS_FRONTEND_EVENT_RECORDING_STOPPED: owner->OnRecordingStopped(); break; case OBS_FRONTEND_EVENT_RECORDING_PAUSED: owner->OnRecordingPaused(); break; case OBS_FRONTEND_EVENT_RECORDING_UNPAUSED: owner->OnRecordingResumed(); break; case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING: owner->OnReplayStarting(); break; case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED: owner->OnReplayStarted(); break; case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING: owner->OnReplayStopping(); break; case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED: owner->OnReplayStopped(); break; case OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED: owner->OnStudioModeSwitched(true); break; case OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED: owner->OnStudioModeSwitched(false); break; case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED: owner->OnPreviewSceneChanged(); break; case OBS_FRONTEND_EVENT_EXIT: owner->unhookTransitionPlaybackEvents(); owner->OnExit(); owner->_srv->stop(); break; } } void WSEvents::broadcastUpdate(const char* updateType, obs_data_t* additionalFields = nullptr) { std::optional streamTime; if (obs_frontend_streaming_active()) { streamTime = std::make_optional(getStreamingTime()); } std::optional recordingTime; if (obs_frontend_recording_active()) { recordingTime = std::make_optional(getRecordingTime()); } RpcEvent event(QString(updateType), streamTime, recordingTime, additionalFields); _srv->broadcast(event); } void WSEvents::connectSourceSignals(obs_source_t* source) { if (!source) { return; } // Disconnect everything first to avoid double-binding disconnectSourceSignals(source); obs_source_type sourceType = obs_source_get_type(source); signal_handler_t* sh = obs_source_get_signal_handler(source); signal_handler_connect(sh, "rename", OnSourceRename, this); signal_handler_connect(sh, "mute", OnSourceMuteStateChange, this); signal_handler_connect(sh, "volume", OnSourceVolumeChange, this); signal_handler_connect(sh, "audio_sync", OnSourceAudioSyncOffsetChanged, this); signal_handler_connect(sh, "audio_mixers", OnSourceAudioMixersChanged, this); signal_handler_connect(sh, "audio_activate", OnSourceAudioActivated, this); signal_handler_connect(sh, "audio_deactivate", OnSourceAudioDeactivated, this); signal_handler_connect(sh, "filter_add", OnSourceFilterAdded, this); signal_handler_connect(sh, "filter_remove", OnSourceFilterRemoved, this); signal_handler_connect(sh, "reorder_filters", OnSourceFilterOrderChanged, this); signal_handler_connect(sh, "media_play", OnMediaPlaying, this); signal_handler_connect(sh, "media_pause", OnMediaPaused, this); signal_handler_connect(sh, "media_restart", OnMediaRestarted, this); signal_handler_connect(sh, "media_stopped", OnMediaStopped, this); signal_handler_connect(sh, "media_next", OnMediaNext, this); signal_handler_connect(sh, "media_previous", OnMediaPrevious, this); signal_handler_connect(sh, "media_started", OnMediaStarted, this); signal_handler_connect(sh, "media_ended", OnMediaEnded, this); if (sourceType == OBS_SOURCE_TYPE_SCENE) { signal_handler_connect(sh, "reorder", OnSceneReordered, this); signal_handler_connect(sh, "item_add", OnSceneItemAdd, this); signal_handler_connect(sh, "item_remove", OnSceneItemDelete, this); signal_handler_connect(sh, "item_visible", OnSceneItemVisibilityChanged, this); signal_handler_connect(sh, "item_locked", OnSceneItemLockChanged, this); signal_handler_connect(sh, "item_transform", OnSceneItemTransform, this); signal_handler_connect(sh, "item_select", OnSceneItemSelected, this); signal_handler_connect(sh, "item_deselect", OnSceneItemDeselected, this); } } void WSEvents::disconnectSourceSignals(obs_source_t* source) { if (!source) { return; } signal_handler_t* sh = obs_source_get_signal_handler(source); signal_handler_disconnect(sh, "rename", OnSourceRename, this); signal_handler_disconnect(sh, "mute", OnSourceMuteStateChange, this); signal_handler_disconnect(sh, "volume", OnSourceVolumeChange, this); signal_handler_disconnect(sh, "audio_sync", OnSourceAudioSyncOffsetChanged, this); signal_handler_disconnect(sh, "audio_mixers", OnSourceAudioMixersChanged, this); signal_handler_disconnect(sh, "audio_activate", OnSourceAudioActivated, this); signal_handler_disconnect(sh, "audio_deactivate", OnSourceAudioDeactivated, this); signal_handler_disconnect(sh, "filter_add", OnSourceFilterAdded, this); signal_handler_disconnect(sh, "filter_remove", OnSourceFilterRemoved, this); signal_handler_disconnect(sh, "reorder_filters", OnSourceFilterOrderChanged, this); signal_handler_disconnect(sh, "reorder", OnSceneReordered, this); signal_handler_disconnect(sh, "item_add", OnSceneItemAdd, this); signal_handler_disconnect(sh, "item_remove", OnSceneItemDelete, this); signal_handler_disconnect(sh, "item_visible", OnSceneItemVisibilityChanged, this); signal_handler_disconnect(sh, "item_locked", OnSceneItemLockChanged, this); signal_handler_disconnect(sh, "item_transform", OnSceneItemTransform, this); signal_handler_disconnect(sh, "item_select", OnSceneItemSelected, this); signal_handler_disconnect(sh, "item_deselect", OnSceneItemDeselected, this); signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this); signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this); signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this); signal_handler_disconnect(sh, "media_play", OnMediaPlaying, this); signal_handler_disconnect(sh, "media_pause", OnMediaPaused, this); signal_handler_disconnect(sh, "media_restart", OnMediaRestarted, this); signal_handler_disconnect(sh, "media_stopped", OnMediaStopped, this); signal_handler_disconnect(sh, "media_next", OnMediaNext, this); signal_handler_disconnect(sh, "media_previous", OnMediaPrevious, this); signal_handler_disconnect(sh, "media_started", OnMediaStarted, this); signal_handler_disconnect(sh, "media_ended", OnMediaEnded, this); } void WSEvents::connectFilterSignals(obs_source_t* filter) { if (!filter) { return; } signal_handler_t* sh = obs_source_get_signal_handler(filter); signal_handler_connect(sh, "enable", OnSourceFilterVisibilityChanged, this); } void WSEvents::disconnectFilterSignals(obs_source_t* filter) { if (!filter) { return; } signal_handler_t* sh = obs_source_get_signal_handler(filter); signal_handler_disconnect(sh, "enable", OnSourceFilterVisibilityChanged, this); } void WSEvents::hookTransitionPlaybackEvents() { obs_frontend_source_list transitions = {}; obs_frontend_get_transitions(&transitions); for (uint i = 0; i < transitions.sources.num; i++) { obs_source_t* transition = transitions.sources.array[i]; signal_handler_t* sh = obs_source_get_signal_handler(transition); signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this); signal_handler_connect(sh, "transition_start", OnTransitionBegin, this); signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this); signal_handler_connect(sh, "transition_stop", OnTransitionEnd, this); signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this); signal_handler_connect(sh, "transition_video_stop", OnTransitionVideoEnd, this); } obs_frontend_source_list_free(&transitions); } void WSEvents::unhookTransitionPlaybackEvents() { obs_frontend_source_list transitions = {}; obs_frontend_get_transitions(&transitions); for (uint i = 0; i < transitions.sources.num; i++) { obs_source_t* transition = transitions.sources.array[i]; signal_handler_t* sh = obs_source_get_signal_handler(transition); signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this); signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this); signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this); } obs_frontend_source_list_free(&transitions); } uint64_t getOutputRunningTime(obs_output_t* output) { if (!output || !obs_output_active(output)) { return 0; } video_t* video = obs_output_video(output); uint64_t frameTimeNs = video_output_get_frame_time(video); int totalFrames = obs_output_get_total_frames(output); return (((uint64_t)totalFrames) * frameTimeNs); } uint64_t WSEvents::getStreamingTime() { OBSOutputAutoRelease streamingOutput = obs_frontend_get_streaming_output(); return getOutputRunningTime(streamingOutput); } uint64_t WSEvents::getRecordingTime() { OBSOutputAutoRelease recordingOutput = obs_frontend_get_recording_output(); return getOutputRunningTime(recordingOutput); } QString WSEvents::getStreamingTimecode() { return Utils::nsToTimestamp(getStreamingTime()); } QString WSEvents::getRecordingTimecode() { return Utils::nsToTimestamp(getRecordingTime()); } OBSDataAutoRelease getMediaSourceData(calldata_t* data) { OBSDataAutoRelease fields = obs_data_create(); OBSSource source = calldata_get_pointer(data, "source"); obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); obs_data_set_string(fields, "sourceKind", obs_source_get_id(source)); return fields; } /** * Indicates a scene change. * * @return {String} `scene-name` The new scene. * @return {Array} `sources` List of scene items in the new scene. Same specification as [`GetCurrentScene`](#getcurrentscene). * * @api events * @name SwitchScenes * @category scenes * @since 0.3 */ void WSEvents::OnSceneChange() { OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene(); OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(currentScene); OBSDataAutoRelease data = obs_data_create(); obs_data_set_string(data, "scene-name", obs_source_get_name(currentScene)); obs_data_set_array(data, "sources", sceneItems); broadcastUpdate("SwitchScenes", data); } /** * The scene list has been modified. * Scenes have been added, removed, or renamed. * * Note: This event is not fired when the scenes are reordered. * * @return {Array} `scenes` Scenes list. * * @api events * @name ScenesChanged * @category scenes * @since 0.3 */ void WSEvents::OnSceneListChange() { OBSDataArrayAutoRelease scenes = Utils::GetScenes(); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_array(fields, "scenes", scenes); broadcastUpdate("ScenesChanged", fields); } /** * Triggered when switching to another scene collection or when renaming the current scene collection. * * @return {String} `sceneCollection` Name of the new current scene collection. * * @api events * @name SceneCollectionChanged * @category scenes * @since 4.0.0 */ void WSEvents::OnSceneCollectionChange() { OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "sceneCollection", obs_frontend_get_current_scene_collection()); broadcastUpdate("SceneCollectionChanged", fields); OnTransitionListChange(); OnTransitionChange(); OnSceneListChange(); OnSceneChange(); } /** * Triggered when a scene collection is created, added, renamed, or removed. * * @return {Array} `sceneCollections` Scene collections list. * @return {String} `sceneCollections.*.name` Scene collection name. * * @api events * @name SceneCollectionListChanged * @category scenes * @since 4.0.0 */ void WSEvents::OnSceneCollectionListChange() { char** sceneCollections = obs_frontend_get_scene_collections(); OBSDataArrayAutoRelease sceneCollectionsList = Utils::StringListToArray(sceneCollections, "name"); bfree(sceneCollections); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_array(fields, "sceneCollections", sceneCollectionsList); broadcastUpdate("SceneCollectionListChanged", fields); } /** * 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() { OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition(); OBSDataAutoRelease data = obs_data_create(); obs_data_set_string(data, "transition-name", obs_source_get_name(currentTransition)); broadcastUpdate("SwitchTransition", data); } /** * The list of available transitions has been modified. * Transitions have been added, removed, or renamed. * * @return {Array} `transitions` Transitions list. * @return {String} `transitions.*.name` Transition name. * * @api events * @name TransitionListChanged * @category transitions * @since 4.0.0 */ void WSEvents::OnTransitionListChange() { obs_frontend_source_list transitionList = {}; obs_frontend_get_transitions(&transitionList); OBSDataArrayAutoRelease transitions = obs_data_array_create(); for (size_t i = 0; i < transitionList.sources.num; i++) { OBSSource transition = transitionList.sources.array[i]; OBSDataAutoRelease obj = obs_data_create(); obs_data_set_string(obj, "name", obs_source_get_name(transition)); obs_data_array_push_back(transitions, obj); } obs_frontend_source_list_free(&transitionList); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_array(fields, "transitions", transitions); broadcastUpdate("TransitionListChanged", fields); } /** * Triggered when switching to another profile or when renaming the current profile. * * @return {String} `profile` Name of the new current profile. * * @api events * @name ProfileChanged * @category profiles * @since 4.0.0 */ void WSEvents::OnProfileChange() { OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "profile", obs_frontend_get_current_profile()); broadcastUpdate("ProfileChanged", fields); } /** * Triggered when a profile is created, added, renamed, or removed. * * @return {Array} `profiles` Profiles list. * @return {String} `profiles.*.name` Profile name. * * @api events * @name ProfileListChanged * @category profiles * @since 4.0.0 */ void WSEvents::OnProfileListChange() { char** profiles = obs_frontend_get_profiles(); OBSDataArrayAutoRelease profilesList = Utils::StringListToArray(profiles, "name"); bfree(profiles); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_array(fields, "profiles", profilesList); broadcastUpdate("ProfileListChanged", fields); } /** * 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() { OBSDataAutoRelease data = obs_data_create(); obs_data_set_bool(data, "preview-only", false); broadcastUpdate("StreamStarting", data); } /** * Streaming started successfully. * * @api events * @name StreamStarted * @category streaming * @since 0.3 */ void WSEvents::OnStreamStarted() { _streamStarttime = 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() { OBSDataAutoRelease data = obs_data_create(); obs_data_set_bool(data, "preview-only", false); broadcastUpdate("StreamStopping", data); } /** * Streaming stopped successfully. * * @api events * @name StreamStopped * @category streaming * @since 0.3 */ void WSEvents::OnStreamStopped() { _streamStarttime = 0; broadcastUpdate("StreamStopped"); } /** * A request to start recording has been issued. * * Note: `recordingFilename` is not provided in this event because this information * is not available at the time this event is emitted. * * @api events * @name RecordingStarting * @category recording * @since 0.3 */ void WSEvents::OnRecordingStarting() { broadcastUpdate("RecordingStarting"); } /** * Recording started successfully. * * @return {String} `recordingFilename` Absolute path to the file of the current recording. * * @api events * @name RecordingStarted * @category recording * @since 0.3 */ void WSEvents::OnRecordingStarted() { OBSDataAutoRelease data = obs_data_create(); obs_data_set_string(data, "recordingFilename", Utils::GetCurrentRecordingFilename()); broadcastUpdate("RecordingStarted", data); } /** * A request to stop recording has been issued. * * @return {String} `recordingFilename` Absolute path to the file of the current recording. * * @api events * @name RecordingStopping * @category recording * @since 0.3 */ void WSEvents::OnRecordingStopping() { OBSDataAutoRelease data = obs_data_create(); obs_data_set_string(data, "recordingFilename", Utils::GetCurrentRecordingFilename()); broadcastUpdate("RecordingStopping", data); } /** * Recording stopped successfully. * * @return {String} `recordingFilename` Absolute path to the file of the current recording. * * @api events * @name RecordingStopped * @category recording * @since 0.3 */ void WSEvents::OnRecordingStopped() { OBSDataAutoRelease data = obs_data_create(); obs_data_set_string(data, "recordingFilename", Utils::GetCurrentRecordingFilename()); broadcastUpdate("RecordingStopped", data); } /** * Current recording paused * * @api events * @name RecordingPaused * @category recording * @since 4.7.0 */ void WSEvents::OnRecordingPaused() { broadcastUpdate("RecordingPaused"); } /** * Current recording resumed * * @api events * @name RecordingResumed * @category recording * @since 4.7.0 */ void WSEvents::OnRecordingResumed() { broadcastUpdate("RecordingResumed"); } /** * 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 stop 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"); } /** * Emitted every 2 seconds when stream is active. * * @return {boolean} `streaming` Current streaming state. * @return {boolean} `recording` Current recording state. * @return {boolean} `replay-buffer-active` Replay Buffer status * @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. * @return {int} `render-total-frames` Number of frames rendered * @return {int} `render-missed-frames` Number of frames missed due to rendering lag * @return {int} `output-total-frames` Number of frames outputted * @return {int} `output-skipped-frames` Number of frames skipped due to encoding lag * @return {double} `average-frame-time` Average frame time (in milliseconds) * @return {double} `cpu-usage` Current CPU usage (percentage) * @return {double} `memory-usage` Current RAM usage (in megabytes) * @return {double} `free-disk-space` Free recording disk space (in megabytes) * @return {boolean} `preview-only` Always false (retrocompatibility). * * @api events * @name StreamStatus * @category streaming * @since 0.3 */ void WSEvents::StreamStatus() { bool streamingActive = obs_frontend_streaming_active(); bool recordingActive = obs_frontend_recording_active(); bool recordingPaused = obs_frontend_recording_paused(); bool replayBufferActive = obs_frontend_replay_buffer_active(); OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output(); if (!streamOutput || !streamingActive) { return; } uint64_t bytesSent = obs_output_get_total_bytes(streamOutput); uint64_t bytesSentTime = os_gettime_ns(); if (bytesSent < _lastBytesSent) bytesSent = 0; if (bytesSent == 0) _lastBytesSent = 0; uint64_t bytesBetween = bytesSent - _lastBytesSent; double timePassed = double(bytesSentTime - _lastBytesSentTime) / 1000000000.0; uint64_t bytesPerSec = bytesBetween / timePassed; _lastBytesSent = bytesSent; _lastBytesSentTime = bytesSentTime; uint64_t totalStreamTime = (getStreamingTime() / 1000000000ULL); int totalFrames = obs_output_get_total_frames(streamOutput); int droppedFrames = obs_output_get_frames_dropped(streamOutput); float strain = obs_output_get_congestion(streamOutput); OBSDataAutoRelease data = obs_data_create(); obs_data_set_bool(data, "streaming", streamingActive); obs_data_set_bool(data, "recording", recordingActive); obs_data_set_bool(data, "recording-paused", recordingPaused); obs_data_set_bool(data, "replay-buffer-active", replayBufferActive); obs_data_set_int(data, "bytes-per-sec", bytesPerSec); obs_data_set_int(data, "kbits-per-sec", (bytesPerSec * 8) / 1024); obs_data_set_int(data, "total-stream-time", totalStreamTime); obs_data_set_int(data, "num-total-frames", totalFrames); obs_data_set_int(data, "num-dropped-frames", droppedFrames); obs_data_set_double(data, "strain", strain); // `stats` contains fps, cpu usage, memory usage, render missed frames, ... OBSDataAutoRelease stats = GetStats(); obs_data_apply(data, stats); obs_data_set_bool(data, "preview-only", false); // Retrocompat with OBSRemote broadcastUpdate("StreamStatus", data); } /** * Emitted every 2 seconds after enabling it by calling SetHeartbeat. * * @return {boolean} `pulse` Toggles between every JSON message as an "I am alive" indicator. * @return {string (optional)} `current-profile` Current active profile. * @return {string (optional)} `current-scene` Current active scene. * @return {boolean (optional)} `streaming` Current streaming state. * @return {int (optional)} `total-stream-time` Total time (in seconds) since the stream started. * @return {int (optional)} `total-stream-bytes` Total bytes sent since the stream started. * @return {int (optional)} `total-stream-frames` Total frames streamed since the stream started. * @return {boolean (optional)} `recording` Current recording state. * @return {int (optional)} `total-record-time` Total time (in seconds) since recording started. * @return {int (optional)} `total-record-bytes` Total bytes recorded since the recording started. * @return {int (optional)} `total-record-frames` Total frames recorded since the recording started. * @return {OBSStats} `stats` OBS Stats * * @api events * @name Heartbeat * @category general * @since v0.3 */ void WSEvents::Heartbeat() { if (!HeartbeatIsActive) return; bool streamingActive = obs_frontend_streaming_active(); bool recordingActive = obs_frontend_recording_active(); bool recordingPaused = obs_frontend_recording_paused(); OBSDataAutoRelease data = obs_data_create(); OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output(); OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output(); pulse = !pulse; obs_data_set_bool(data, "pulse", pulse); char* currentProfile = obs_frontend_get_current_profile(); obs_data_set_string(data, "current-profile", currentProfile); bfree(currentProfile); OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene(); obs_data_set_string(data, "current-scene", obs_source_get_name(currentScene)); obs_data_set_bool(data, "streaming", streamingActive); if (streamingActive) { obs_data_set_int(data, "total-stream-time", (getStreamingTime() / 1000000000ULL)); obs_data_set_int(data, "total-stream-bytes", (uint64_t)obs_output_get_total_bytes(streamOutput)); obs_data_set_int(data, "total-stream-frames", obs_output_get_total_frames(streamOutput)); } obs_data_set_bool(data, "recording", recordingActive); obs_data_set_bool(data, "recording-paused", recordingPaused); if (recordingActive) { obs_data_set_int(data, "total-record-time", (getRecordingTime() / 1000000000ULL)); obs_data_set_int(data, "total-record-bytes", (uint64_t)obs_output_get_total_bytes(recordOutput)); obs_data_set_int(data, "total-record-frames", obs_output_get_total_frames(recordOutput)); } OBSDataAutoRelease stats = GetStats(); obs_data_set_obj(data, "stats", stats); broadcastUpdate("Heartbeat", data); } /** * 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) { OBSDataAutoRelease fields = obs_data_create(); obs_data_set_int(fields, "new-duration", ms); broadcastUpdate("TransitionDurationChanged", fields); } /** * A transition (other than "cut") has begun. * * @return {String} `name` Transition name. * @return {String} `type` Transition type. * @return {int} `duration` Transition duration (in milliseconds). * Will be -1 for any transition with a fixed duration, * such as a Stinger, due to limitations of the OBS API. * @return {String} `from-scene` Source scene of the transition * @return {String} `to-scene` Destination scene of the transition * * @api events * @name TransitionBegin * @category transitions * @since 4.0.0 */ void WSEvents::OnTransitionBegin(void* param, calldata_t* data) { auto instance = reinterpret_cast(param); OBSSource transition = calldata_get_pointer(data, "source"); if (!transition) { return; } OBSDataAutoRelease fields = Utils::GetTransitionData(transition); instance->broadcastUpdate("TransitionBegin", fields); } /** * A transition (other than "cut") has ended. * Note: The `from-scene` field is not available in TransitionEnd. * * @return {String} `name` Transition name. * @return {String} `type` Transition type. * @return {int} `duration` Transition duration (in milliseconds). * @return {String} `to-scene` Destination scene of the transition * * @api events * @name TransitionEnd * @category transitions * @since 4.8.0 */ void WSEvents::OnTransitionEnd(void* param, calldata_t* data) { auto instance = reinterpret_cast(param); OBSSource transition = calldata_get_pointer(data, "source"); if (!transition) { return; } OBSDataAutoRelease fields = Utils::GetTransitionData(transition); instance->broadcastUpdate("TransitionEnd", fields); } /** * A stinger transition has finished playing its video. * * @return {String} `name` Transition name. * @return {String} `type` Transition type. * @return {int} `duration` Transition duration (in milliseconds). * @return {String} `from-scene` Source scene of the transition * @return {String} `to-scene` Destination scene of the transition * * @api events * @name TransitionVideoEnd * @category transitions * @since 4.8.0 */ void WSEvents::OnTransitionVideoEnd(void* param, calldata_t* data) { auto instance = reinterpret_cast(param); OBSSource transition = calldata_get_pointer(data, "source"); if (!transition) { return; } OBSDataAutoRelease fields = Utils::GetTransitionData(transition); instance->broadcastUpdate("TransitionVideoEnd", fields); } /** * A source has been created. A source can be an input, a scene or a transition. * * @return {String} `sourceName` Source name * @return {String} `sourceType` Source type. Can be "input", "scene", "transition" or "filter". * @return {String} `sourceKind` Source kind. * @return {Object} `sourceSettings` Source settings * * @api events * @name SourceCreated * @category sources * @since 4.6.0 */ void WSEvents::OnSourceCreate(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSSource source = calldata_get_pointer(data, "source"); if (!source) { return; } self->connectSourceSignals(source); OBSDataAutoRelease sourceSettings = obs_source_get_settings(source); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); obs_data_set_string(fields, "sourceType", sourceTypeToString(obs_source_get_type(source)) ); obs_data_set_string(fields, "sourceKind", obs_source_get_id(source)); obs_data_set_obj(fields, "sourceSettings", sourceSettings); self->broadcastUpdate("SourceCreated", fields); } /** * A source has been destroyed/removed. A source can be an input, a scene or a transition. * * @return {String} `sourceName` Source name * @return {String} `sourceType` Source type. Can be "input", "scene", "transition" or "filter". * @return {String} `sourceKind` Source kind. * * @api events * @name SourceDestroyed * @category sources * @since 4.6.0 */ void WSEvents::OnSourceDestroy(void* param, calldata_t* data) { auto self = reinterpret_cast(param); obs_source_t* source = calldata_get_pointer(data, "source"); if (!source) { return; } self->disconnectSourceSignals(source); obs_source_type sourceType = obs_source_get_type(source); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); obs_data_set_string(fields, "sourceType", sourceTypeToString(sourceType)); obs_data_set_string(fields, "sourceKind", obs_source_get_id(source)); self->broadcastUpdate("SourceDestroyed", fields); } /** * The volume of a source has changed. * * @return {String} `sourceName` Source name * @return {float} `volume` Source volume * * @api events * @name SourceVolumeChanged * @category sources * @since 4.6.0 */ void WSEvents::OnSourceVolumeChange(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSSource source = calldata_get_pointer(data, "source"); if (!source) { return; } double volume = 0; if (!calldata_get_float(data, "volume", &volume)) { return; } OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); obs_data_set_double(fields, "volume", volume); self->broadcastUpdate("SourceVolumeChanged", fields); } /** * A source has been muted or unmuted. * * @return {String} `sourceName` Source name * @return {boolean} `muted` Mute status of the source * * @api events * @name SourceMuteStateChanged * @category sources * @since 4.6.0 */ void WSEvents::OnSourceMuteStateChange(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSSource source = calldata_get_pointer(data, "source"); if (!source) { return; } bool muted = false; if (!calldata_get_bool(data, "muted", &muted)) { return; } OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); obs_data_set_bool(fields, "muted", muted); self->broadcastUpdate("SourceMuteStateChanged", fields); } /** * A source has removed audio. * * @return {String} `sourceName` Source name * * @api events * @name SourceAudioDeactivated * @category sources * @since 4.9.0 */ void WSEvents::OnSourceAudioDeactivated(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSSource source = calldata_get_pointer(data, "source"); if (!source) { return; } OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); self->broadcastUpdate("SourceAudioDeactivated", fields); } /** * A source has added audio. * * @return {String} `sourceName` Source name * * @api events * @name SourceAudioActivated * @category sources * @since 4.9.0 */ void WSEvents::OnSourceAudioActivated(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSSource source = calldata_get_pointer(data, "source"); if (!source) { return; } OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); self->broadcastUpdate("SourceAudioActivated", fields); } /** * The audio sync offset of a source has changed. * * @return {String} `sourceName` Source name * @return {int} `syncOffset` Audio sync offset of the source (in nanoseconds) * * @api events * @name SourceAudioSyncOffsetChanged * @category sources * @since 4.6.0 */ void WSEvents::OnSourceAudioSyncOffsetChanged(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSSource source = calldata_get_pointer(data, "source"); if (!source) { return; } long long syncOffset; if (!calldata_get_int(data, "offset", &syncOffset)) { return; } OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); obs_data_set_int(fields, "syncOffset", (int)syncOffset); self->broadcastUpdate("SourceAudioSyncOffsetChanged", fields); } /** * Audio mixer routing changed on a source. * * @return {String} `sourceName` Source name * @return {Array} `mixers` Routing status of the source for each audio mixer (array of 6 values) * @return {int} `mixers.*.id` Mixer number * @return {boolean} `mixers.*.enabled` Routing status * @return {String} `hexMixersValue` Raw mixer flags (little-endian, one bit per mixer) as an hexadecimal value * * @api events * @name SourceAudioMixersChanged * @category sources * @since 4.6.0 */ void WSEvents::OnSourceAudioMixersChanged(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSSource source = calldata_get_pointer(data, "source"); if (!source) { return; } long long audioMixers; if (!calldata_get_int(data, "mixers", &audioMixers)) { return; } OBSDataArrayAutoRelease mixers = obs_data_array_create(); for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { OBSDataAutoRelease item = obs_data_create(); obs_data_set_int(item, "id", i + 1); obs_data_set_bool(item, "enabled", (1 << i) & audioMixers); obs_data_array_push_back(mixers, item); } const QString hexValue = QString::number(audioMixers, 16).toUpper().prepend("0x"); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); obs_data_set_array(fields, "mixers", mixers); obs_data_set_string(fields, "hexMixersValue", hexValue.toUtf8()); self->broadcastUpdate("SourceAudioMixersChanged", fields); } /** * A source has been renamed. * * @return {String} `previousName` Previous source name * @return {String} `newName` New source name * @return {String} `sourceType` Type of source (input, scene, filter, transition) * * @api events * @name SourceRenamed * @category sources * @since 4.6.0 */ void WSEvents::OnSourceRename(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSSource source = calldata_get_pointer(data, "source"); if (!source) { return; } const char* newName = calldata_get_string(data, "new_name"); if (!newName) { return; } const char* previousName = calldata_get_string(data, "prev_name"); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "previousName", previousName); obs_data_set_string(fields, "newName", newName); obs_data_set_string(fields, "sourceType", sourceTypeToString(obs_source_get_type(source))); // TODO: Split into dedicated events for source/scene. Only doing it this way for backwards compatability until 5.0 self->broadcastUpdate("SourceRenamed", fields); } /** * A filter was added to a source. * * @return {String} `sourceName` Source name * @return {String} `filterName` Filter name * @return {String} `filterType` Filter type * @return {Object} `filterSettings` Filter settings * * @api events * @name SourceFilterAdded * @category sources * @since 4.6.0 */ void WSEvents::OnSourceFilterAdded(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSSource source = calldata_get_pointer(data, "source"); if (!source) { return; } OBSSource filter = calldata_get_pointer(data, "filter"); if (!filter) { return; } self->connectFilterSignals(filter); OBSDataAutoRelease filterSettings = obs_source_get_settings(filter); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); obs_data_set_string(fields, "filterName", obs_source_get_name(filter)); obs_data_set_string(fields, "filterType", obs_source_get_id(filter)); obs_data_set_obj(fields, "filterSettings", filterSettings); self->broadcastUpdate("SourceFilterAdded", fields); } /** * A filter was removed from a source. * * @return {String} `sourceName` Source name * @return {String} `filterName` Filter name * @return {String} `filterType` Filter type * * @api events * @name SourceFilterRemoved * @category sources * @since 4.6.0 */ void WSEvents::OnSourceFilterRemoved(void* param, calldata_t* data) { auto self = reinterpret_cast(param); obs_source_t* source = calldata_get_pointer(data, "source"); if (!source) { return; } obs_source_t* filter = calldata_get_pointer(data, "filter"); if (!filter) { return; } self->disconnectFilterSignals(filter); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); obs_data_set_string(fields, "filterName", obs_source_get_name(filter)); obs_data_set_string(fields, "filterType", obs_source_get_id(filter)); self->broadcastUpdate("SourceFilterRemoved", fields); } /** * The visibility/enabled state of a filter changed * * @return {String} `sourceName` Source name * @return {String} `filterName` Filter name * @return {Boolean} `filterEnabled` New filter state * * @api events * @name SourceFilterVisibilityChanged * @category sources * @since 4.7.0 */ void WSEvents::OnSourceFilterVisibilityChanged(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSSource source = calldata_get_pointer(data, "source"); if (!source) { return; } OBSSource parent = obs_filter_get_parent(source); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "sourceName", obs_source_get_name(parent)); obs_data_set_string(fields, "filterName", obs_source_get_name(source)); obs_data_set_bool(fields, "filterEnabled", obs_source_enabled(source)); self->broadcastUpdate("SourceFilterVisibilityChanged", fields); } /** * Filters in a source have been reordered. * * @return {String} `sourceName` Source name * @return {Array} `filters` Ordered Filters list * @return {String} `filters.*.name` Filter name * @return {String} `filters.*.type` Filter type * @return {boolean} `filters.*.enabled` Filter visibility status * * @api events * @name SourceFiltersReordered * @category sources * @since 4.6.0 */ void WSEvents::OnSourceFilterOrderChanged(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSSource source = calldata_get_pointer(data, "source"); if (!source) { return; } OBSDataArrayAutoRelease filters = Utils::GetSourceFiltersList(source, false); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); obs_data_set_array(fields, "filters", filters); self->broadcastUpdate("SourceFiltersReordered", fields); } /** * A media source has started playing. * * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. * * @return {String} `sourceName` Source name * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) * * @api events * @name MediaPlaying * @category media * @since 4.9.0 */ void WSEvents::OnMediaPlaying(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSDataAutoRelease fields = getMediaSourceData(data); self->broadcastUpdate("MediaPlaying", fields); } /** * A media source has been paused. * * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. * * @return {String} `sourceName` Source name * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) * * @api events * @name MediaPaused * @category media * @since 4.9.0 */ void WSEvents::OnMediaPaused(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSDataAutoRelease fields = getMediaSourceData(data); self->broadcastUpdate("MediaPaused", fields); } /** * A media source has been restarted. * * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. * * @return {String} `sourceName` Source name * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) * * @api events * @name MediaRestarted * @category media * @since 4.9.0 */ void WSEvents::OnMediaRestarted(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSDataAutoRelease fields = getMediaSourceData(data); self->broadcastUpdate("MediaRestarted", fields); } /** * A media source has been stopped. * * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. * * @return {String} `sourceName` Source name * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) * * @api events * @name MediaStopped * @category media * @since 4.9.0 */ void WSEvents::OnMediaStopped(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSDataAutoRelease fields = getMediaSourceData(data); self->broadcastUpdate("MediaStopped", fields); } /** * A media source has gone to the next item in the playlist. * * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. * * @return {String} `sourceName` Source name * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) * * @api events * @name MediaNext * @category media * @since 4.9.0 */ void WSEvents::OnMediaNext(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSDataAutoRelease fields = getMediaSourceData(data); self->broadcastUpdate("MediaNext", fields); } /** * A media source has gone to the previous item in the playlist. * * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. * * @return {String} `sourceName` Source name * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) * * @api events * @name MediaPrevious * @category media * @since 4.9.0 */ void WSEvents::OnMediaPrevious(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSDataAutoRelease fields = getMediaSourceData(data); self->broadcastUpdate("MediaPrevious", fields); } /** * A media source has been started. * * Note: These events are emitted by the OBS sources themselves. For example when the media file starts playing. The behavior depends on the type of media source being used. * * @return {String} `sourceName` Source name * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) * * @api events * @name MediaStarted * @category media * @since 4.9.0 */ void WSEvents::OnMediaStarted(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSDataAutoRelease fields = getMediaSourceData(data); self->broadcastUpdate("MediaStarted", fields); } /** * A media source has ended. * * Note: These events are emitted by the OBS sources themselves. For example when the media file ends. The behavior depends on the type of media source being used. * * @return {String} `sourceName` Source name * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) * * @api events * @name MediaEnded * @category media * @since 4.9.0 */ void WSEvents::OnMediaEnded(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSDataAutoRelease fields = getMediaSourceData(data); self->broadcastUpdate("MediaEnded", fields); } /** * Scene items within a scene have been reordered. * * @return {String} `scene-name` Name of the scene where items have been reordered. * @return {Array} `scene-items` Ordered list of scene items * @return {String} `scene-items.*.source-name` Item source name * @return {int} `scene-items.*.item-id` Scene item unique ID * * @api events * @name SourceOrderChanged * @category scene items * @since 4.0.0 */ void WSEvents::OnSceneReordered(void* param, calldata_t* data) { auto instance = reinterpret_cast(param); OBSScene scene = calldata_get_pointer(data, "scene"); if (!scene) { return; } OBSDataArrayAutoRelease sceneItems = obs_data_array_create(); obs_scene_enum_items(scene, [](obs_scene_t* scene, obs_sceneitem_t* sceneItem, void* param) { obs_data_array_t* sceneItems = reinterpret_cast(param); OBSSource itemSource = obs_sceneitem_get_source(sceneItem); OBSDataAutoRelease item = obs_data_create(); obs_data_set_string(item, "source-name", obs_source_get_name(itemSource)); obs_data_set_int(item, "item-id", obs_sceneitem_get_id(sceneItem)); obs_data_array_push_back(sceneItems, item); return true; }, sceneItems); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "scene-name", obs_source_get_name(obs_scene_get_source(scene))); obs_data_set_array(fields, "scene-items", sceneItems); instance->broadcastUpdate("SourceOrderChanged", fields); } /** * A scene item has been added to a scene. * * @return {String} `scene-name` Name of the scene. * @return {String} `item-name` Name of the item added to the scene. * @return {int} `item-id` Scene item ID * * @api events * @name SceneItemAdded * @category scene items * @since 4.0.0 */ void WSEvents::OnSceneItemAdd(void* param, calldata_t* data) { auto instance = reinterpret_cast(param); obs_scene_t* scene = nullptr; calldata_get_ptr(data, "scene", &scene); obs_sceneitem_t* sceneItem = nullptr; calldata_get_ptr(data, "item", &sceneItem); const char* sceneName = obs_source_get_name(obs_scene_get_source(scene)); const char* sceneItemName = obs_source_get_name(obs_sceneitem_get_source(sceneItem)); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "scene-name", sceneName); obs_data_set_string(fields, "item-name", sceneItemName); obs_data_set_int(fields, "item-id", obs_sceneitem_get_id(sceneItem)); instance->broadcastUpdate("SceneItemAdded", fields); } /** * A scene item has been removed from a scene. * * @return {String} `scene-name` Name of the scene. * @return {String} `item-name` Name of the item removed from the scene. * @return {int} `item-id` Scene item ID * * @api events * @name SceneItemRemoved * @category scene items * @since 4.0.0 */ void WSEvents::OnSceneItemDelete(void* param, calldata_t* data) { auto instance = reinterpret_cast(param); obs_scene_t* scene = nullptr; calldata_get_ptr(data, "scene", &scene); obs_sceneitem_t* sceneItem = nullptr; calldata_get_ptr(data, "item", &sceneItem); const char* sceneName = obs_source_get_name(obs_scene_get_source(scene)); const char* sceneItemName = obs_source_get_name(obs_sceneitem_get_source(sceneItem)); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "scene-name", sceneName); obs_data_set_string(fields, "item-name", sceneItemName); obs_data_set_int(fields, "item-id", obs_sceneitem_get_id(sceneItem)); instance->broadcastUpdate("SceneItemRemoved", fields); } /** * A scene 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 {int} `item-id` Scene item ID * @return {boolean} `item-visible` New visibility state of the item. * * @api events * @name SceneItemVisibilityChanged * @category scene items * @since 4.0.0 */ void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) { auto instance = reinterpret_cast(param); obs_scene_t* scene = nullptr; calldata_get_ptr(data, "scene", &scene); obs_sceneitem_t* sceneItem = nullptr; calldata_get_ptr(data, "item", &sceneItem); bool visible = false; calldata_get_bool(data, "visible", &visible); const char* sceneName = obs_source_get_name(obs_scene_get_source(scene)); const char* sceneItemName = obs_source_get_name(obs_sceneitem_get_source(sceneItem)); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "scene-name", sceneName); obs_data_set_string(fields, "item-name", sceneItemName); obs_data_set_int(fields, "item-id", obs_sceneitem_get_id(sceneItem)); obs_data_set_bool(fields, "item-visible", visible); instance->broadcastUpdate("SceneItemVisibilityChanged", fields); } /** * A scene item's locked status has been toggled. * * @return {String} `scene-name` Name of the scene. * @return {String} `item-name` Name of the item in the scene. * @return {int} `item-id` Scene item ID * @return {boolean} `item-locked` New locked state of the item. * * @api events * @name SceneItemLockChanged * @category scene items * @since 4.8.0 */ void WSEvents::OnSceneItemLockChanged(void* param, calldata_t* data) { auto instance = reinterpret_cast(param); obs_scene_t* scene = nullptr; calldata_get_ptr(data, "scene", &scene); obs_sceneitem_t* sceneItem = nullptr; calldata_get_ptr(data, "item", &sceneItem); bool locked = false; calldata_get_bool(data, "locked", &locked); const char* sceneName = obs_source_get_name(obs_scene_get_source(scene)); const char* sceneItemName = obs_source_get_name(obs_sceneitem_get_source(sceneItem)); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "scene-name", sceneName); obs_data_set_string(fields, "item-name", sceneItemName); obs_data_set_int(fields, "item-id", obs_sceneitem_get_id(sceneItem)); obs_data_set_bool(fields, "item-locked", locked); instance->broadcastUpdate("SceneItemLockChanged", fields); } /** * A scene item's transform has been changed. * * @return {String} `scene-name` Name of the scene. * @return {String} `item-name` Name of the item in the scene. * @return {int} `item-id` Scene item ID * @return {SceneItemTransform} `transform` Scene item transform properties * * @api events * @name SceneItemTransformChanged * @category scene items * @since 4.6.0 */ void WSEvents::OnSceneItemTransform(void* param, calldata_t* data) { auto instance = reinterpret_cast(param); obs_scene_t* scene = nullptr; calldata_get_ptr(data, "scene", &scene); obs_sceneitem_t* sceneItem = nullptr; calldata_get_ptr(data, "item", &sceneItem); const char* sceneName = obs_source_get_name(obs_scene_get_source(scene)); const char* sceneItemName = obs_source_get_name(obs_sceneitem_get_source(sceneItem)); OBSDataAutoRelease transform = Utils::GetSceneItemPropertiesData(sceneItem); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "scene-name", sceneName); obs_data_set_string(fields, "item-name", sceneItemName); obs_data_set_int(fields, "item-id", obs_sceneitem_get_id(sceneItem)); obs_data_set_obj(fields, "transform", transform); instance->broadcastUpdate("SceneItemTransformChanged", fields); } /** * A scene item is selected. * * @return {String} `scene-name` Name of the scene. * @return {String} `item-name` Name of the item in the scene. * @return {int} `item-id` Name of the item in the scene. * * @api events * @name SceneItemSelected * @category scene items * @since 4.6.0 */ void WSEvents::OnSceneItemSelected(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSScene scene = calldata_get_pointer(data, "scene"); if (!scene) { return; } OBSSceneItem item = calldata_get_pointer(data, "item"); if (!item) { return; } OBSSource sceneSource = obs_scene_get_source(scene); OBSSource itemSource = obs_sceneitem_get_source(item); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "scene-name", obs_source_get_name(sceneSource)); obs_data_set_string(fields, "item-name", obs_source_get_name(itemSource)); obs_data_set_int(fields, "item-id", obs_sceneitem_get_id(item)); self->broadcastUpdate("SceneItemSelected", fields); } /** * A scene item is deselected. * * @return {String} `scene-name` Name of the scene. * @return {String} `item-name` Name of the item in the scene. * @return {int} `item-id` Name of the item in the scene. * * @api events * @name SceneItemDeselected * @category scene items * @since 4.6.0 */ void WSEvents::OnSceneItemDeselected(void* param, calldata_t* data) { auto self = reinterpret_cast(param); OBSScene scene = calldata_get_pointer(data, "scene"); if (!scene) { return; } OBSSceneItem item = calldata_get_pointer(data, "item"); if (!item) { return; } OBSSource sceneSource = obs_scene_get_source(scene); OBSSource itemSource = obs_sceneitem_get_source(item); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "scene-name", obs_source_get_name(sceneSource)); obs_data_set_string(fields, "item-name", obs_source_get_name(itemSource)); obs_data_set_int(fields, "item-id", obs_sceneitem_get_id(item)); self->broadcastUpdate("SceneItemDeselected", fields); } /** * The selected preview scene has changed (only available in Studio Mode). * * @return {String} `scene-name` Name of the scene being previewed. * @return {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::OnPreviewSceneChanged() { if (obs_frontend_preview_program_mode_active()) { OBSSourceAutoRelease scene = obs_frontend_get_current_preview_scene(); if (!scene) return; OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(scene); OBSDataAutoRelease data = obs_data_create(); obs_data_set_string(data, "scene-name", obs_source_get_name(scene)); obs_data_set_array(data, "sources", sceneItems); broadcastUpdate("PreviewSceneChanged", 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::OnStudioModeSwitched(bool checked) { OBSDataAutoRelease data = obs_data_create(); obs_data_set_bool(data, "new-state", checked); broadcastUpdate("StudioModeSwitched", data); } /** * A custom broadcast message, sent by the server, requested by one of the websocket clients. * * @return {String} `realm` Identifier provided by the sender * @return {Object} `data` User-defined data * * @api events * @name BroadcastCustomMessage * @category general * @since 4.7.0 */ void WSEvents::OnBroadcastCustomMessage(QString realm, obs_data_t* data) { OBSDataAutoRelease broadcastData = obs_data_create(); obs_data_set_string(broadcastData, "realm", realm.toUtf8().constData()); obs_data_set_obj(broadcastData, "data", data); broadcastUpdate("BroadcastCustomMessage", broadcastData); } /** * @typedef {Object} `OBSStats` * @property {double} `fps` Current framerate. * @property {int} `render-total-frames` Number of frames rendered * @property {int} `render-missed-frames` Number of frames missed due to rendering lag * @property {int} `output-total-frames` Number of frames outputted * @property {int} `output-skipped-frames` Number of frames skipped due to encoding lag * @property {double} `average-frame-time` Average frame render time (in milliseconds) * @property {double} `cpu-usage` Current CPU usage (percentage) * @property {double} `memory-usage` Current RAM usage (in megabytes) * @property {double} `free-disk-space` Free recording disk space (in megabytes) */ obs_data_t* WSEvents::GetStats() { obs_data_t* stats = obs_data_create(); double cpuUsage = os_cpu_usage_info_query(cpuUsageInfo); double memoryUsage = (double)os_get_proc_resident_size() / (1024.0 * 1024.0); video_t* mainVideo = obs_get_video(); uint32_t outputTotalFrames = video_output_get_total_frames(mainVideo); uint32_t outputSkippedFrames = video_output_get_skipped_frames(mainVideo); double averageFrameTime = (double)obs_get_average_frame_time_ns() / 1000000.0; config_t* currentProfile = obs_frontend_get_profile_config(); const char* outputMode = config_get_string(currentProfile, "Output", "Mode"); const char* path = strcmp(outputMode, "Advanced") ? config_get_string(currentProfile, "SimpleOutput", "FilePath") : config_get_string(currentProfile, "AdvOut", "RecFilePath"); double freeDiskSpace = (double)os_get_free_disk_space(path) / (1024.0 * 1024.0); obs_data_set_double(stats, "fps", obs_get_active_fps()); obs_data_set_int(stats, "render-total-frames", obs_get_total_frames()); obs_data_set_int(stats, "render-missed-frames", obs_get_lagged_frames()); obs_data_set_int(stats, "output-total-frames", outputTotalFrames); obs_data_set_int(stats, "output-skipped-frames", outputSkippedFrames); obs_data_set_double(stats, "average-frame-time", averageFrameTime); obs_data_set_double(stats, "cpu-usage", cpuUsage); obs_data_set_double(stats, "memory-usage", memoryUsage); obs_data_set_double(stats, "free-disk-space", freeDiskSpace); return stats; } obs-websocket-4.9.0/src/WSEvents.h000066400000000000000000000114231401107467600167600ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2019 Stéphane Lepin Copyright (C) 2017 Brendan Hagan 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 */ #pragma once #include #include #include #include #include #include #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); }; obs-websocket-4.9.0/src/WSRequestHandler.cpp000066400000000000000000000236041401107467600210010ustar00rootroot00000000000000/** * obs-websocket * Copyright (C) 2016-2017 Stéphane Lepin * Copyright (C) 2017 Mikhail Swift * * 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 */ #include #include #include "Config.h" #include "Utils.h" #include "WSRequestHandler.h" using namespace std::placeholders; const QHash 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 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); } obs-websocket-4.9.0/src/WSRequestHandler.h000066400000000000000000000201221401107467600204360ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2019 Stéphane Lepin Copyright (C) 2017 Mikhail Swift 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 */ #pragma once #include #include #include #include #include #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 messageMap; static const QSet 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&); }; obs-websocket-4.9.0/src/WSRequestHandler_General.cpp000066400000000000000000000370131401107467600224350ustar00rootroot00000000000000#include "WSRequestHandler.h" #include #include #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 names = messageMap.keys(); QList 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 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(" 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} `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} `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); } obs-websocket-4.9.0/src/WSRequestHandler_MediaControl.cpp000066400000000000000000000274041401107467600234430ustar00rootroot00000000000000#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} `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); } obs-websocket-4.9.0/src/WSRequestHandler_Outputs.cpp000066400000000000000000000135621401107467600225460ustar00rootroot00000000000000#include #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 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} `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(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(); }); } obs-websocket-4.9.0/src/WSRequestHandler_Profiles.cpp000066400000000000000000000041151401107467600226400ustar00rootroot00000000000000#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(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} `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); } obs-websocket-4.9.0/src/WSRequestHandler_Recording.cpp000066400000000000000000000116431401107467600227750ustar00rootroot00000000000000#include "obs-websocket.h" #include "WSRequestHandler.h" #include #include #include "Utils.h" #include "WSEvents.h" RpcResponse ifCanPause(const RpcRequest& request, std::function 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); } obs-websocket-4.9.0/src/WSRequestHandler_ReplayBuffer.cpp000066400000000000000000000055041401107467600234460ustar00rootroot00000000000000#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(); } obs-websocket-4.9.0/src/WSRequestHandler_SceneCollections.cpp000066400000000000000000000045101401107467600243100ustar00rootroot00000000000000#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(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} `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); } obs-websocket-4.9.0/src/WSRequestHandler_SceneItems.cpp000066400000000000000000000675011401107467600231240ustar00rootroot00000000000000#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} `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 (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(_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); } obs-websocket-4.9.0/src/WSRequestHandler_Scenes.cpp000066400000000000000000000230431401107467600222760ustar00rootroot00000000000000#include "Utils.h" #include "WSRequestHandler.h" /** * @typedef {Object} `Scene` * @property {String} `name` Name of the currently active scene. * @property {Array} `sources` Ordered list of the current scene's source items. */ /** * Switch to the specified scene. * * @param {String} `scene-name` Name of the scene to switch to. * * @api requests * @name SetCurrentScene * @category scenes * @since 0.3 */ RpcResponse WSRequestHandler::SetCurrentScene(const RpcRequest& request) { if (!request.hasField("scene-name")) { return request.failed("missing request parameters"); } const char* sceneName = obs_data_get_string(request.parameters(), "scene-name"); OBSSourceAutoRelease source = obs_get_source_by_name(sceneName); if (source) { obs_frontend_set_current_scene(source); return request.success(); } else { return request.failed("requested scene does not exist"); } } /** * Get the current scene's name and source items. * * @return {String} `name` Name of the currently active scene. * @return {Array} `sources` Ordered list of the current scene's source items. * * @api requests * @name GetCurrentScene * @category scenes * @since 0.3 */ RpcResponse WSRequestHandler::GetCurrentScene(const RpcRequest& request) { OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene(); OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(currentScene); OBSDataAutoRelease data = obs_data_create(); obs_data_set_string(data, "name", obs_source_get_name(currentScene)); obs_data_set_array(data, "sources", sceneItems); return request.success(data); } /** * Get a list of scenes in the currently active profile. * * @return {String} `current-scene` Name of the currently active scene. * @return {Array} `scenes` Ordered list of the current profile's scenes (See [GetCurrentScene](#getcurrentscene) for more information). * * @api requests * @name GetSceneList * @category scenes * @since 0.3 */ RpcResponse WSRequestHandler::GetSceneList(const RpcRequest& request) { OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene(); OBSDataArrayAutoRelease scenes = Utils::GetScenes(); OBSDataAutoRelease data = obs_data_create(); obs_data_set_string(data, "current-scene", obs_source_get_name(currentScene)); obs_data_set_array(data, "scenes", scenes); return request.success(data); } /** * Create a new scene scene. * * @param {String} `sceneName` Name of the scene to create. * * @api requests * @name CreateScene * @category scenes * @since 4.9.0 */ RpcResponse WSRequestHandler::CreateScene(const RpcRequest& request) { if (!request.hasField("sceneName")) { return request.failed("missing request parameters"); } const char* sceneName = obs_data_get_string(request.parameters(), "sceneName"); OBSSourceAutoRelease source = obs_get_source_by_name(sceneName); if (source) { return request.failed("scene with this name already exists"); } obs_scene_create(sceneName); return request.success(); } /** * Changes the order of scene items in the requested scene. * * @param {String (optional)} `scene` Name of the scene to reorder (defaults to current). * @param {Array} `items` Ordered list of objects with name and/or id specified. Id preferred due to uniqueness per scene * @param {int (optional)} `items.*.id` Id of a specific scene item. Unique on a scene by scene basis. * @param {String (optional)} `items.*.name` Name of a scene item. Sufficiently unique if no scene items share sources within the scene. * * @api requests * @name ReorderSceneItems * @category scenes * @since 4.5.0 */ RpcResponse WSRequestHandler::ReorderSceneItems(const RpcRequest& request) { QString sceneName = obs_data_get_string(request.parameters(), "scene"); OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName); if (!scene) { return request.failed("requested scene doesn't exist"); } OBSDataArrayAutoRelease items = obs_data_get_array(request.parameters(), "items"); if (!items) { return request.failed("sceneItem order not specified"); } struct reorder_context { obs_data_array_t* items; bool success; QString errorMessage; }; struct reorder_context ctx; ctx.success = false; ctx.items = items; obs_scene_atomic_update(scene, [](void* param, obs_scene_t* scene) { auto ctx = reinterpret_cast(param); QVector orderList; struct obs_sceneitem_order_info info; size_t itemCount = obs_data_array_count(ctx->items); for (uint i = 0; i < itemCount; i++) { OBSDataAutoRelease item = obs_data_array_item(ctx->items, i); OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item); if (!sceneItem) { ctx->success = false; ctx->errorMessage = "Invalid sceneItem id or name specified"; return; } info.group = nullptr; info.item = sceneItem; orderList.insert(0, info); } ctx->success = obs_scene_reorder_items2(scene, orderList.data(), orderList.size()); if (!ctx->success) { ctx->errorMessage = "Invalid sceneItem order"; } }, &ctx); if (!ctx.success) { return request.failed(ctx.errorMessage); } return request.success(); } /** * Set a scene to use a specific transition override. * * @param {String} `sceneName` Name of the scene to switch to. * @param {String} `transitionName` Name of the transition to use. * @param {int (Optional)} `transitionDuration` Duration in milliseconds of the transition if transition is not fixed. Defaults to the current duration specified in the UI if there is no current override and this value is not given. * * @api requests * @name SetSceneTransitionOverride * @category scenes * @since 4.8.0 */ RpcResponse WSRequestHandler::SetSceneTransitionOverride(const RpcRequest& request) { if (!request.hasField("sceneName") || !request.hasField("transitionName")) { return request.failed("missing request parameters"); } QString sceneName = obs_data_get_string(request.parameters(), "sceneName"); OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8()); if (!source) { return request.failed("requested scene does not exist"); } enum obs_source_type sourceType = obs_source_get_type(source); if (sourceType != OBS_SOURCE_TYPE_SCENE) { return request.failed("requested scene is invalid"); } QString transitionName = obs_data_get_string(request.parameters(), "transitionName"); if (!Utils::GetTransitionFromName(transitionName)) { return request.failed("requested transition does not exist"); } OBSDataAutoRelease sourceData = obs_source_get_private_settings(source); obs_data_set_string(sourceData, "transition", transitionName.toUtf8().constData()); if (request.hasField("transitionDuration")) { int transitionOverrideDuration = obs_data_get_int(request.parameters(), "transitionDuration"); obs_data_set_int(sourceData, "transition_duration", transitionOverrideDuration); } else if(!obs_data_has_user_value(sourceData, "transition_duration")) { obs_data_set_int(sourceData, "transition_duration", obs_frontend_get_transition_duration() ); } return request.success(); } /** * Remove any transition override on a scene. * * @param {String} `sceneName` Name of the scene to switch to. * * @api requests * @name RemoveSceneTransitionOverride * @category scenes * @since 4.8.0 */ RpcResponse WSRequestHandler::RemoveSceneTransitionOverride(const RpcRequest& request) { if (!request.hasField("sceneName")) { return request.failed("missing request parameters"); } QString sceneName = obs_data_get_string(request.parameters(), "sceneName"); OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8()); if (!source) { return request.failed("requested scene does not exist"); } enum obs_source_type sourceType = obs_source_get_type(source); if (sourceType != OBS_SOURCE_TYPE_SCENE) { return request.failed("requested scene is invalid"); } OBSDataAutoRelease sourceData = obs_source_get_private_settings(source); obs_data_erase(sourceData, "transition"); obs_data_erase(sourceData, "transition_duration"); return request.success(); } /** * Get the current scene transition override. * * @param {String} `sceneName` Name of the scene to switch to. * * @return {String} `transitionName` Name of the current overriding transition. Empty string if no override is set. * @return {int} `transitionDuration` Transition duration. `-1` if no override is set. * * @api requests * @name GetSceneTransitionOverride * @category scenes * @since 4.8.0 */ RpcResponse WSRequestHandler::GetSceneTransitionOverride(const RpcRequest& request) { if (!request.hasField("sceneName")) { return request.failed("missing request parameters"); } QString sceneName = obs_data_get_string(request.parameters(), "sceneName"); OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8()); if (!source) { return request.failed("requested scene does not exist"); } enum obs_source_type sourceType = obs_source_get_type(source); if (sourceType != OBS_SOURCE_TYPE_SCENE) { return request.failed("requested scene is invalid"); } OBSDataAutoRelease sourceData = obs_source_get_private_settings(source); const char* transitionOverrideName = obs_data_get_string(sourceData, "transition"); bool hasDurationOverride = obs_data_has_user_value(sourceData, "transition_duration"); int transitionOverrideDuration = obs_data_get_int(sourceData, "transition_duration"); OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "transitionName", transitionOverrideName); obs_data_set_int(fields, "transitionDuration", (hasDurationOverride ? transitionOverrideDuration : -1) ); return request.success(fields); } obs-websocket-4.9.0/src/WSRequestHandler_Sources.cpp000066400000000000000000002003721401107467600225030ustar00rootroot00000000000000#include #include #include #include #include #include "Utils.h" #include "WSRequestHandler.h" bool isTextGDIPlusSource(const QString& sourceKind) { return (sourceKind == "text_gdiplus" || sourceKind == "text_gdiplus_v2"); } bool isTextFreeType2Source(const QString& sourceKind) { return (sourceKind == "text_ft2_source" || sourceKind == "text_ft2_source_v2"); } /** * Create a source and add it as a sceneitem to a scene. * * @param {String} `sourceName` Source name. * @param {String} `sourceKind` Source kind, Eg. `vlc_source`. * @param {String} `sceneName` Scene to add the new source to. * @param {Object (optional)} `sourceSettings` Source settings data. * @param {boolean (optional)} `setVisible` Set the created SceneItem as visible or not. Defaults to true * * @return {int} `itemId` ID of the SceneItem in the scene. * * @api requests * @name CreateSource * @category sources * @since 4.9.0 */ RpcResponse WSRequestHandler::CreateSource(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("sourceKind") || !request.hasField("sceneName")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); QString sourceKind = obs_data_get_string(request.parameters(), "sourceKind"); if (sourceName.isEmpty() || sourceKind.isEmpty()) { return request.failed("empty sourceKind or sourceName parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (source) { return request.failed("a source with that name already exists"); } 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"); } OBSDataAutoRelease sourceSettings = nullptr; if (request.hasField("sourceSettings")) { sourceSettings = obs_data_get_obj(request.parameters(), "sourceSettings"); } OBSSourceAutoRelease newSource = obs_source_create(sourceKind.toUtf8(), sourceName.toUtf8(), sourceSettings, nullptr); if (!newSource) { return request.failed("failed to create the source"); } obs_source_set_enabled(newSource, true); Utils::AddSourceData data; data.source = newSource; 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); } /** * List all sources available in the running OBS instance * * @return {Array} `sources` Array of sources * @return {String} `sources.*.name` Unique source name * @return {String} `sources.*.typeId` Non-unique source internal type (a.k.a kind) * @return {String} `sources.*.type` Source type. Value is one of the following: "input", "filter", "transition", "scene" or "unknown" * * @api requests * @name GetSourcesList * @category sources * @since 4.3.0 */ RpcResponse WSRequestHandler::GetSourcesList(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; OBSDataAutoRelease sourceData = obs_data_create(); obs_data_set_string(sourceData, "name", obs_source_get_name(source)); obs_data_set_string(sourceData, "typeId", obs_source_get_id(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_FILTER: typeString = "filter"; break; case OBS_SOURCE_TYPE_TRANSITION: typeString = "transition"; break; case OBS_SOURCE_TYPE_SCENE: typeString = "scene"; break; default: typeString = "unknown"; break; } obs_data_set_string(sourceData, "type", typeString.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, "sources", sourcesArray); return request.success(response); } /** * Get a list of all available sources types * * @return {Array} `types` Array of source types * @return {String} `types.*.typeId` Non-unique internal source type ID * @return {String} `types.*.displayName` Display name of the source type * @return {String} `types.*.type` Type. Value is one of the following: "input", "filter", "transition" or "other" * @return {Object} `types.*.defaultSettings` Default settings of this source type * @return {Object} `types.*.caps` Source type capabilities * @return {Boolean} `types.*.caps.isAsync` True if source of this type provide frames asynchronously * @return {Boolean} `types.*.caps.hasVideo` True if sources of this type provide video * @return {Boolean} `types.*.caps.hasAudio` True if sources of this type provide audio * @return {Boolean} `types.*.caps.canInteract` True if interaction with this sources of this type is possible * @return {Boolean} `types.*.caps.isComposite` True if sources of this type composite one or more sub-sources * @return {Boolean} `types.*.caps.doNotDuplicate` True if sources of this type should not be fully duplicated * @return {Boolean} `types.*.caps.doNotSelfMonitor` True if sources of this type may cause a feedback loop if it's audio is monitored and shouldn't be * * @api requests * @name GetSourceTypesList * @category sources * @since 4.3.0 */ RpcResponse WSRequestHandler::GetSourceTypesList(const RpcRequest& request) { OBSDataArrayAutoRelease idsArray = obs_data_array_create(); const char* id; size_t idx = 0; QHash idTypes; idx = 0; while (obs_enum_input_types(idx++, &id)) { idTypes.insert(id, "input"); } idx = 0; while (obs_enum_filter_types(idx++, &id)) { idTypes.insert(id, "filter"); } idx = 0; while (obs_enum_transition_types(idx++, &id)) { idTypes.insert(id, "transition"); } idx = 0; while (obs_enum_source_types(idx++, &id)) { OBSDataAutoRelease item = obs_data_create(); obs_data_set_string(item, "typeId", id); obs_data_set_string(item, "displayName", obs_source_get_display_name(id)); obs_data_set_string(item, "type", idTypes.value(id, "other").toUtf8()); uint32_t caps = obs_get_source_output_flags(id); OBSDataAutoRelease capsData = obs_data_create(); obs_data_set_bool(capsData, "isAsync", caps & OBS_SOURCE_ASYNC); obs_data_set_bool(capsData, "hasVideo", caps & OBS_SOURCE_VIDEO); obs_data_set_bool(capsData, "hasAudio", caps & OBS_SOURCE_AUDIO); obs_data_set_bool(capsData, "canInteract", caps & OBS_SOURCE_INTERACTION); obs_data_set_bool(capsData, "isComposite", caps & OBS_SOURCE_COMPOSITE); obs_data_set_bool(capsData, "doNotDuplicate", caps & OBS_SOURCE_DO_NOT_DUPLICATE); obs_data_set_bool(capsData, "doNotSelfMonitor", caps & OBS_SOURCE_DO_NOT_SELF_MONITOR); obs_data_set_bool(capsData, "isDeprecated", caps & OBS_SOURCE_DEPRECATED); obs_data_set_obj(item, "caps", capsData); OBSDataAutoRelease defaultSettings = obs_get_source_defaults(id); obs_data_set_obj(item, "defaultSettings", defaultSettings); obs_data_array_push_back(idsArray, item); } OBSDataAutoRelease response = obs_data_create(); obs_data_set_array(response, "types", idsArray); return request.success(response); } /** * Get the volume of the specified source. Default response uses mul format, NOT SLIDER PERCENTAGE. * * @param {String} `source` Source name. * @param {boolean (optional)} `useDecibel` Output volume in decibels of attenuation instead of amplitude/mul. * * @return {String} `name` Source name. * @return {double} `volume` Volume of the source. Between `0.0` and `20.0` if using mul, under `26.0` if using dB. * @return {boolean} `muted` Indicates whether the source is muted. * * @api requests * @name GetVolume * @category sources * @since 4.0.0 */ RpcResponse WSRequestHandler::GetVolume(const RpcRequest& request) { if (!request.hasField("source")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "source"); 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"); } float volume = obs_source_get_volume(source); bool useDecibel = obs_data_get_bool(request.parameters(), "useDecibel"); if (useDecibel) { volume = obs_mul_to_db(volume); } if (volume == -INFINITY) { volume = -100.0; } OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "name", obs_source_get_name(source)); obs_data_set_double(response, "volume", volume); obs_data_set_bool(response, "muted", obs_source_muted(source)); return request.success(response); } /** * Set the volume of the specified source. Default request format uses mul, NOT SLIDER PERCENTAGE. * * @param {String} `source` Source name. * @param {double} `volume` Desired volume. Must be between `0.0` and `20.0` for mul, and under 26.0 for dB. OBS will interpret dB values under -100.0 as Inf. Note: The OBS volume sliders only reach a maximum of 1.0mul/0.0dB, however OBS actually supports larger values. * @param {boolean (optional)} `useDecibel` Interperet `volume` data as decibels instead of amplitude/mul. * * @api requests * @name SetVolume * @category sources * @since 4.0.0 */ RpcResponse WSRequestHandler::SetVolume(const RpcRequest& request) { if (!request.hasField("source") || !request.hasField("volume")) { return request.failed("missing request parameters"); } bool useDecibel = obs_data_get_bool(request.parameters(), "useDecibel"); QString sourceName = obs_data_get_string(request.parameters(), "source"); float sourceVolume = obs_data_get_double(request.parameters(), "volume"); bool isNotValidDecibel = (useDecibel && sourceVolume > 26.0); bool isNotValidMul = (!useDecibel && (sourceVolume < 0.0 || sourceVolume > 20.0)); if (sourceName.isEmpty() || isNotValidDecibel || isNotValidMul) { 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"); } if (useDecibel) { sourceVolume = obs_db_to_mul(sourceVolume); } obs_source_set_volume(source, sourceVolume); return request.success(); } /** * Get the mute status of a specified source. * * @param {String} `source` Source name. * * @return {String} `name` Source name. * @return {boolean} `muted` Mute status of the source. * * @api requests * @name GetMute * @category sources * @since 4.0.0 */ RpcResponse WSRequestHandler::GetMute(const RpcRequest& request) { if (!request.hasField("source")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "source"); 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, "name", obs_source_get_name(source)); obs_data_set_bool(response, "muted", obs_source_muted(source)); return request.success(response); } /** * Sets the mute status of a specified source. * * @param {String} `source` Source name. * @param {boolean} `mute` Desired mute status. * * @api requests * @name SetMute * @category sources * @since 4.0.0 */ RpcResponse WSRequestHandler::SetMute(const RpcRequest& request) { if (!request.hasField("source") || !request.hasField("mute")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "source"); bool mute = obs_data_get_bool(request.parameters(), "mute"); 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_set_muted(source, mute); return request.success(); } /** * Inverts the mute status of a specified source. * * @param {String} `source` Source name. * * @api requests * @name ToggleMute * @category sources * @since 4.0.0 */ RpcResponse WSRequestHandler::ToggleMute(const RpcRequest& request) { if (!request.hasField("source")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "source"); if (sourceName.isEmpty()) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { return request.failed("invalid request parameters"); } obs_source_set_muted(source, !obs_source_muted(source)); return request.success(); } /** * Get the audio's active status of a specified source. * * @param {String} `sourceName` Source name. * * @return {boolean} `audioActive` Audio active status of the source. * * @api requests * @name GetAudioActive * @category sources * @since 4.9.0 */ RpcResponse WSRequestHandler::GetAudioActive(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_bool(response, "audioActive", obs_source_audio_active(source)); return request.success(response); } /** * Sets (aka rename) the name of a source. Also works with scenes since scenes are technically sources in OBS. * * Note: If the new name already exists as a source, obs-websocket will return an error. * * @param {String} `sourceName` Source name. * @param {String} `newName` New source name. * * @api requests * @name SetSourceName * @category sources * @since 4.8.0 */ RpcResponse WSRequestHandler::SetSourceName(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("newName")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); QString newName = obs_data_get_string(request.parameters(), "newName"); if (sourceName.isEmpty() || newName.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"); } OBSSourceAutoRelease existingSource = obs_get_source_by_name(newName.toUtf8()); if (!existingSource) { // OBS is supposed to automatically rename colliding source names, but it doesn't. So this gets to be the solution for now. obs_source_set_name(source, newName.toUtf8()); return request.success(); } else { return request.failed("a source with that name already exists"); } } /** * Set the audio sync offset of a specified source. * * @param {String} `source` Source name. * @param {int} `offset` The desired audio sync offset (in nanoseconds). * * @api requests * @name SetSyncOffset * @category sources * @since 4.2.0 */ RpcResponse WSRequestHandler::SetSyncOffset(const RpcRequest& request) { if (!request.hasField("source") || !request.hasField("offset")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "source"); int64_t sourceSyncOffset = (int64_t)obs_data_get_int(request.parameters(), "offset"); 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_set_sync_offset(source, sourceSyncOffset); return request.success(); } /** * Get the audio sync offset of a specified source. * * @param {String} `source` Source name. * * @return {String} `name` Source name. * @return {int} `offset` The audio sync offset (in nanoseconds). * * @api requests * @name GetSyncOffset * @category sources * @since 4.2.0 */ RpcResponse WSRequestHandler::GetSyncOffset(const RpcRequest& request) { if (!request.hasField("source")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "source"); 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, "name", obs_source_get_name(source)); obs_data_set_int(response, "offset", obs_source_get_sync_offset(source)); return request.success(response); } /** * Get settings of the specified source * * @param {String} `sourceName` Source name. * @param {String (optional)} `sourceType` Type of the specified source. Useful for type-checking if you expect a specific settings schema. * * @return {String} `sourceName` Source name * @return {String} `sourceType` Type of the specified source * @return {Object} `sourceSettings` Source settings (varies between source types, may require some probing around). * * @api requests * @name GetSourceSettings * @category sources * @since 4.3.0 */ RpcResponse WSRequestHandler::GetSourceSettings(const RpcRequest& request) { if (!request.hasField("sourceName")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } if (request.hasField("sourceType")) { QString actualSourceType = obs_source_get_id(source); QString requestedType = obs_data_get_string(request.parameters(), "sourceType"); if (actualSourceType != requestedType) { return request.failed("specified source exists but is not of expected type"); } } OBSDataAutoRelease sourceSettings = obs_source_get_settings(source); OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "sourceName", obs_source_get_name(source)); obs_data_set_string(response, "sourceType", obs_source_get_id(source)); obs_data_set_obj(response, "sourceSettings", sourceSettings); return request.success(response); } /** * Set settings of the specified source. * * @param {String} `sourceName` Source name. * @param {String (optional)} `sourceType` Type of the specified source. Useful for type-checking to avoid settings a set of settings incompatible with the actual source's type. * @param {Object} `sourceSettings` Source settings (varies between source types, may require some probing around). * * @return {String} `sourceName` Source name * @return {String} `sourceType` Type of the specified source * @return {Object} `sourceSettings` Updated source settings * * @api requests * @name SetSourceSettings * @category sources * @since 4.3.0 */ RpcResponse WSRequestHandler::SetSourceSettings(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("sourceSettings")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } if (request.hasField("sourceType")) { QString actualSourceType = obs_source_get_id(source); QString requestedType = obs_data_get_string(request.parameters(), "sourceType"); if (actualSourceType != requestedType) { return request.failed("specified source exists but is not of expected type"); } } OBSDataAutoRelease newSettings = obs_data_get_obj(request.parameters(), "sourceSettings"); obs_source_update(source, newSettings); obs_source_update_properties(source); OBSDataAutoRelease updatedSettings = obs_source_get_settings(source); OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "sourceName", obs_source_get_name(source)); obs_data_set_string(response, "sourceType", obs_source_get_id(source)); obs_data_set_obj(response, "sourceSettings", updatedSettings); return request.success(response); } /** * Get the current properties of a Text GDI Plus source. * * @param {String} `source` Source name. * * @return {String} `source` Source name. * @return {String} `align` Text Alignment ("left", "center", "right"). * @return {int} `bk_color` Background color. * @return {int} `bk_opacity` Background opacity (0-100). * @return {boolean} `chatlog` Chat log. * @return {int} `chatlog_lines` Chat log lines. * @return {int} `color` Text color. * @return {boolean} `extents` Extents wrap. * @return {int} `extents_cx` Extents cx. * @return {int} `extents_cy` Extents cy. * @return {String} `file` File path name. * @return {boolean} `read_from_file` Read text from the specified file. * @return {Object} `font` Holds data for the font. Ex: `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }` * @return {String} `font.face` Font face. * @return {int} `font.flags` Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8` * @return {int} `font.size` Font text size. * @return {String} `font.style` Font Style (unknown function). * @return {boolean} `gradient` Gradient enabled. * @return {int} `gradient_color` Gradient color. * @return {float} `gradient_dir` Gradient direction. * @return {int} `gradient_opacity` Gradient opacity (0-100). * @return {boolean} `outline` Outline. * @return {int} `outline_color` Outline color. * @return {int} `outline_size` Outline size. * @return {int} `outline_opacity` Outline opacity (0-100). * @return {String} `text` Text content to be displayed. * @return {String} `valign` Text vertical alignment ("top", "center", "bottom"). * @return {boolean} `vertical` Vertical text enabled. * * @api requests * @name GetTextGDIPlusProperties * @category sources * @since 4.1.0 */ RpcResponse WSRequestHandler::GetTextGDIPlusProperties(const RpcRequest& request) { const char* sourceName = obs_data_get_string(request.parameters(), "source"); if (!sourceName) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } QString sourceKind = obs_source_get_id(source); if (!isTextGDIPlusSource(sourceKind)) { return request.failed("not a text gdi plus source"); } OBSDataAutoRelease response = obs_source_get_settings(source); obs_data_set_string(response, "source", obs_source_get_name(source)); return request.success(response); } /** * Set the current properties of a Text GDI Plus source. * * @param {String} `source` Name of the source. * @param {String (optional)} `align` Text Alignment ("left", "center", "right"). * @param {int (optional)} `bk_color` Background color. * @param {int (optional)} `bk_opacity` Background opacity (0-100). * @param {boolean (optional)} `chatlog` Chat log. * @param {int (optional)} `chatlog_lines` Chat log lines. * @param {int (optional)} `color` Text color. * @param {boolean (optional)} `extents` Extents wrap. * @param {int (optional)} `extents_cx` Extents cx. * @param {int (optional)} `extents_cy` Extents cy. * @param {String (optional)} `file` File path name. * @param {boolean (optional)} `read_from_file` Read text from the specified file. * @param {Object (optional)} `font` Holds data for the font. Ex: `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }` * @param {String (optional)} `font.face` Font face. * @param {int (optional)} `font.flags` Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8` * @param {int (optional)} `font.size` Font text size. * @param {String (optional)} `font.style` Font Style (unknown function). * @param {boolean (optional)} `gradient` Gradient enabled. * @param {int (optional)} `gradient_color` Gradient color. * @param {float (optional)} `gradient_dir` Gradient direction. * @param {int (optional)} `gradient_opacity` Gradient opacity (0-100). * @param {boolean (optional)} `outline` Outline. * @param {int (optional)} `outline_color` Outline color. * @param {int (optional)} `outline_size` Outline size. * @param {int (optional)} `outline_opacity` Outline opacity (0-100). * @param {String (optional)} `text` Text content to be displayed. * @param {String (optional)} `valign` Text vertical alignment ("top", "center", "bottom"). * @param {boolean (optional)} `vertical` Vertical text enabled. * @param {boolean (optional)} `render` Visibility of the scene item. * * @api requests * @name SetTextGDIPlusProperties * @category sources * @since 4.1.0 */ RpcResponse WSRequestHandler::SetTextGDIPlusProperties(const RpcRequest& request) { if (!request.hasField("source")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "source"); if (!sourceName) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } QString sourceKind = obs_source_get_id(source); if (!isTextGDIPlusSource(sourceKind)) { return request.failed("not a text gdi plus source"); } OBSDataAutoRelease settings = obs_source_get_settings(source); if (request.hasField("align")) { obs_data_set_string(settings, "align", obs_data_get_string(request.parameters(), "align")); } if (request.hasField("bk_color")) { obs_data_set_int(settings, "bk_color", obs_data_get_int(request.parameters(), "bk_color")); } if (request.hasField("bk-opacity")) { obs_data_set_int(settings, "bk_opacity", obs_data_get_int(request.parameters(), "bk_opacity")); } if (request.hasField("chatlog")) { obs_data_set_bool(settings, "chatlog", obs_data_get_bool(request.parameters(), "chatlog")); } if (request.hasField("chatlog_lines")) { obs_data_set_int(settings, "chatlog_lines", obs_data_get_int(request.parameters(), "chatlog_lines")); } if (request.hasField("color")) { obs_data_set_int(settings, "color", obs_data_get_int(request.parameters(), "color")); } if (request.hasField("extents")) { obs_data_set_bool(settings, "extents", obs_data_get_bool(request.parameters(), "extents")); } if (request.hasField("extents_wrap")) { obs_data_set_bool(settings, "extents_wrap", obs_data_get_bool(request.parameters(), "extents_wrap")); } if (request.hasField("extents_cx")) { obs_data_set_int(settings, "extents_cx", obs_data_get_int(request.parameters(), "extents_cx")); } if (request.hasField("extents_cy")) { obs_data_set_int(settings, "extents_cy", obs_data_get_int(request.parameters(), "extents_cy")); } if (request.hasField("file")) { obs_data_set_string(settings, "file", obs_data_get_string(request.parameters(), "file")); } if (request.hasField("font")) { OBSDataAutoRelease font_obj = obs_data_get_obj(settings, "font"); if (font_obj) { OBSDataAutoRelease req_font_obj = obs_data_get_obj(request.parameters(), "font"); if (obs_data_has_user_value(req_font_obj, "face")) { obs_data_set_string(font_obj, "face", obs_data_get_string(req_font_obj, "face")); } if (obs_data_has_user_value(req_font_obj, "flags")) { obs_data_set_int(font_obj, "flags", obs_data_get_int(req_font_obj, "flags")); } if (obs_data_has_user_value(req_font_obj, "size")) { obs_data_set_int(font_obj, "size", obs_data_get_int(req_font_obj, "size")); } if (obs_data_has_user_value(req_font_obj, "style")) { obs_data_set_string(font_obj, "style", obs_data_get_string(req_font_obj, "style")); } } } if (request.hasField("gradient")) { obs_data_set_bool(settings, "gradient", obs_data_get_bool(request.parameters(), "gradient")); } if (request.hasField("gradient_color")) { obs_data_set_int(settings, "gradient_color", obs_data_get_int(request.parameters(), "gradient_color")); } if (request.hasField("gradient_dir")) { obs_data_set_double(settings, "gradient_dir", obs_data_get_double(request.parameters(), "gradient_dir")); } if (request.hasField("gradient_opacity")) { obs_data_set_int(settings, "gradient_opacity", obs_data_get_int(request.parameters(), "gradient_opacity")); } if (request.hasField("outline")) { obs_data_set_bool(settings, "outline", obs_data_get_bool(request.parameters(), "outline")); } if (request.hasField("outline_size")) { obs_data_set_int(settings, "outline_size", obs_data_get_int(request.parameters(), "outline_size")); } if (request.hasField("outline_color")) { obs_data_set_int(settings, "outline_color", obs_data_get_int(request.parameters(), "outline_color")); } if (request.hasField("outline_opacity")) { obs_data_set_int(settings, "outline_opacity", obs_data_get_int(request.parameters(), "outline_opacity")); } if (request.hasField("read_from_file")) { obs_data_set_bool(settings, "read_from_file", obs_data_get_bool(request.parameters(), "read_from_file")); } if (request.hasField("text")) { obs_data_set_string(settings, "text", obs_data_get_string(request.parameters(), "text")); } if (request.hasField("valign")) { obs_data_set_string(settings, "valign", obs_data_get_string(request.parameters(), "valign")); } if (request.hasField("vertical")) { obs_data_set_bool(settings, "vertical", obs_data_get_bool(request.parameters(), "vertical")); } obs_source_update(source, settings); return request.success(); } /** * Get the current properties of a Text Freetype 2 source. * * @param {String} `source` Source name. * * @return {String} `source` Source name * @return {int} `color1` Gradient top color. * @return {int} `color2` Gradient bottom color. * @return {int} `custom_width` Custom width (0 to disable). * @return {boolean} `drop_shadow` Drop shadow. * @return {Object} `font` Holds data for the font. Ex: `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }` * @return {String} `font.face` Font face. * @return {int} `font.flags` Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8` * @return {int} `font.size` Font text size. * @return {String} `font.style` Font Style (unknown function). * @return {boolean} `from_file` Read text from the specified file. * @return {boolean} `log_mode` Chat log. * @return {boolean} `outline` Outline. * @return {String} `text` Text content to be displayed. * @return {String} `text_file` File path. * @return {boolean} `word_wrap` Word wrap. * * @api requests * @name GetTextFreetype2Properties * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::GetTextFreetype2Properties(const RpcRequest& request) { const char* sourceName = obs_data_get_string(request.parameters(), "source"); if (!sourceName) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } QString sourceKind = obs_source_get_id(source); if (!isTextFreeType2Source(sourceKind)) { return request.failed("not a freetype 2 source"); } OBSDataAutoRelease response = obs_source_get_settings(source); obs_data_set_string(response, "source", sourceName); return request.success(response); } /** * Set the current properties of a Text Freetype 2 source. * * @param {String} `source` Source name. * @param {int (optional)} `color1` Gradient top color. * @param {int (optional)} `color2` Gradient bottom color. * @param {int (optional)} `custom_width` Custom width (0 to disable). * @param {boolean (optional)} `drop_shadow` Drop shadow. * @param {Object (optional)} `font` Holds data for the font. Ex: `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }` * @param {String (optional)} `font.face` Font face. * @param {int (optional)} `font.flags` Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8` * @param {int (optional)} `font.size` Font text size. * @param {String (optional)} `font.style` Font Style (unknown function). * @param {boolean (optional)} `from_file` Read text from the specified file. * @param {boolean (optional)} `log_mode` Chat log. * @param {boolean (optional)} `outline` Outline. * @param {String (optional)} `text` Text content to be displayed. * @param {String (optional)} `text_file` File path. * @param {boolean (optional)} `word_wrap` Word wrap. * * @api requests * @name SetTextFreetype2Properties * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::SetTextFreetype2Properties(const RpcRequest& request) { const char* sourceName = obs_data_get_string(request.parameters(), "source"); if (!sourceName) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } QString sourceKind = obs_source_get_id(source); if (!isTextFreeType2Source(sourceKind)) { return request.failed("not text freetype 2 source"); } OBSDataAutoRelease settings = obs_source_get_settings(source); if (request.hasField("color1")) { obs_data_set_int(settings, "color1", obs_data_get_int(request.parameters(), "color1")); } if (request.hasField("color2")) { obs_data_set_int(settings, "color2", obs_data_get_int(request.parameters(), "color2")); } if (request.hasField("custom_width")) { obs_data_set_int(settings, "custom_width", obs_data_get_int(request.parameters(), "custom_width")); } if (request.hasField("drop_shadow")) { obs_data_set_bool(settings, "drop_shadow", obs_data_get_bool(request.parameters(), "drop_shadow")); } if (request.hasField("font")) { OBSDataAutoRelease font_obj = obs_data_get_obj(settings, "font"); if (font_obj) { OBSDataAutoRelease req_font_obj = obs_data_get_obj(request.parameters(), "font"); if (obs_data_has_user_value(req_font_obj, "face")) { obs_data_set_string(font_obj, "face", obs_data_get_string(req_font_obj, "face")); } if (obs_data_has_user_value(req_font_obj, "flags")) { obs_data_set_int(font_obj, "flags", obs_data_get_int(req_font_obj, "flags")); } if (obs_data_has_user_value(req_font_obj, "size")) { obs_data_set_int(font_obj, "size", obs_data_get_int(req_font_obj, "size")); } if (obs_data_has_user_value(req_font_obj, "style")) { obs_data_set_string(font_obj, "style", obs_data_get_string(req_font_obj, "style")); } } } if (request.hasField("from_file")) { obs_data_set_bool(settings, "from_file", obs_data_get_bool(request.parameters(), "from_file")); } if (request.hasField("log_mode")) { obs_data_set_bool(settings, "log_mode", obs_data_get_bool(request.parameters(), "log_mode")); } if (request.hasField("outline")) { obs_data_set_bool(settings, "outline", obs_data_get_bool(request.parameters(), "outline")); } if (request.hasField("text")) { obs_data_set_string(settings, "text", obs_data_get_string(request.parameters(), "text")); } if (request.hasField("text_file")) { obs_data_set_string(settings, "text_file", obs_data_get_string(request.parameters(), "text_file")); } if (request.hasField("word_wrap")) { obs_data_set_bool(settings, "word_wrap", obs_data_get_bool(request.parameters(), "word_wrap")); } obs_source_update(source, settings); return request.success(); } /** * Get current properties for a Browser Source. * * @param {String} `source` Source name. * * @return {String} `source` Source name. * @return {boolean} `is_local_file` Indicates that a local file is in use. * @return {String} `local_file` file path. * @return {String} `url` Url. * @return {String} `css` CSS to inject. * @return {int} `width` Width. * @return {int} `height` Height. * @return {int} `fps` Framerate. * @return {boolean} `shutdown` Indicates whether the source should be shutdown when not visible. * * @api requests * @name GetBrowserSourceProperties * @category sources * @since 4.1.0 * @deprecated Since 4.8.0. Prefer the use of GetSourceSettings. Will be removed in v5.0.0 */ RpcResponse WSRequestHandler::GetBrowserSourceProperties(const RpcRequest& request) { const char* sourceName = obs_data_get_string(request.parameters(), "source"); if (!sourceName) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } QString sourceId = obs_source_get_id(source); if (sourceId != "browser_source" && sourceId != "linuxbrowser-source") { return request.failed("not a browser source"); } OBSDataAutoRelease response = obs_source_get_settings(source); obs_data_set_string(response, "source", obs_source_get_name(source)); return request.success(response); } /** * Set current properties for a Browser Source. * * @param {String} `source` Name of the source. * @param {boolean (optional)} `is_local_file` Indicates that a local file is in use. * @param {String (optional)} `local_file` file path. * @param {String (optional)} `url` Url. * @param {String (optional)} `css` CSS to inject. * @param {int (optional)} `width` Width. * @param {int (optional)} `height` Height. * @param {int (optional)} `fps` Framerate. * @param {boolean (optional)} `shutdown` Indicates whether the source should be shutdown when not visible. * @param {boolean (optional)} `render` Visibility of the scene item. * * @api requests * @name SetBrowserSourceProperties * @category sources * @deprecated Since 4.8.0. Prefer the use of SetSourceSettings. Will be removed in v5.0.0 * @since 4.1.0 */ RpcResponse WSRequestHandler::SetBrowserSourceProperties(const RpcRequest& request) { if (!request.hasField("source")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "source"); if (!sourceName) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } QString sourceId = obs_source_get_id(source); if(sourceId != "browser_source" && sourceId != "linuxbrowser-source") { return request.failed("not a browser source"); } OBSDataAutoRelease settings = obs_source_get_settings(source); if (request.hasField("restart_when_active")) { obs_data_set_bool(settings, "restart_when_active", obs_data_get_bool(request.parameters(), "restart_when_active")); } if (request.hasField("shutdown")) { obs_data_set_bool(settings, "shutdown", obs_data_get_bool(request.parameters(), "shutdown")); } if (request.hasField("is_local_file")) { obs_data_set_bool(settings, "is_local_file", obs_data_get_bool(request.parameters(), "is_local_file")); } if (request.hasField("local_file")) { obs_data_set_string(settings, "local_file", obs_data_get_string(request.parameters(), "local_file")); } if (request.hasField("url")) { obs_data_set_string(settings, "url", obs_data_get_string(request.parameters(), "url")); } if (request.hasField("css")) { obs_data_set_string(settings, "css", obs_data_get_string(request.parameters(), "css")); } if (request.hasField("width")) { obs_data_set_int(settings, "width", obs_data_get_int(request.parameters(), "width")); } if (request.hasField("height")) { obs_data_set_int(settings, "height", obs_data_get_int(request.parameters(), "height")); } if (request.hasField("fps")) { obs_data_set_int(settings, "fps", obs_data_get_int(request.parameters(), "fps")); } obs_source_update(source, settings); return request.success(); } /** * Get configured special sources like Desktop Audio and Mic/Aux sources. * * @return {String (optional)} `desktop-1` Name of the first Desktop Audio capture source. * @return {String (optional)} `desktop-2` Name of the second Desktop Audio capture source. * @return {String (optional)} `mic-1` Name of the first Mic/Aux input source. * @return {String (optional)} `mic-2` Name of the second Mic/Aux input source. * @return {String (optional)} `mic-3` NAme of the third Mic/Aux input source. * * @api requests * @name GetSpecialSources * @category sources * @since 4.1.0 */ RpcResponse WSRequestHandler::GetSpecialSources(const RpcRequest& request) { OBSDataAutoRelease response = obs_data_create(); QMap sources; sources["desktop-1"] = 1; sources["desktop-2"] = 2; sources["mic-1"] = 3; sources["mic-2"] = 4; sources["mic-3"] = 5; QMapIterator i(sources); while (i.hasNext()) { i.next(); const char* id = i.key(); OBSSourceAutoRelease source = obs_get_output_source(i.value()); if (source) { obs_data_set_string(response, id, obs_source_get_name(source)); } } return request.success(response); } /** * List filters applied to a source * * @param {String} `sourceName` Source name * * @return {Array} `filters` List of filters for the specified source * @return {Boolean} `filters.*.enabled` Filter status (enabled or not) * @return {String} `filters.*.type` Filter type * @return {String} `filters.*.name` Filter name * @return {Object} `filters.*.settings` Filter settings * * @api requests * @name GetSourceFilters * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::GetSourceFilters(const RpcRequest& request) { if (!request.hasField("sourceName")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } OBSDataArrayAutoRelease filters = Utils::GetSourceFiltersList(source, true); OBSDataAutoRelease response = obs_data_create(); obs_data_set_array(response, "filters", filters); return request.success(response); } /** * List filters applied to a source * * @param {String} `sourceName` Source name * @param {String} `filterName` Source filter name * * @return {Boolean} `enabled` Filter status (enabled or not) * @return {String} `type` Filter type * @return {String} `name` Filter name * @return {Object} `settings` Filter settings * * @api requests * @name GetSourceFilterInfo * @category sources * @since 4.7.0 */ RpcResponse WSRequestHandler::GetSourceFilterInfo(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("filterName")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } const char* filterName = obs_data_get_string(request.parameters(), "filterName"); OBSSourceAutoRelease filter = obs_source_get_filter_by_name(source, filterName); if (!filter) { return request.failed("specified filter doesn't exist on specified source"); } OBSDataAutoRelease response = Utils::GetSourceFilterInfo(filter, true); return request.success(response); } /** * Add a new filter to a source. Available source types along with their settings properties are available from `GetSourceTypesList`. * * @param {String} `sourceName` Name of the source on which the filter is added * @param {String} `filterName` Name of the new filter * @param {String} `filterType` Filter type * @param {Object} `filterSettings` Filter settings * * @api requests * @name AddFilterToSource * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::AddFilterToSource(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("filterName") || !request.hasField("filterType") || !request.hasField("filterSettings")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); const char* filterName = obs_data_get_string(request.parameters(), "filterName"); const char* filterType = obs_data_get_string(request.parameters(), "filterType"); OBSDataAutoRelease filterSettings = obs_data_get_obj(request.parameters(), "filterSettings"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } OBSSourceAutoRelease existingFilter = obs_source_get_filter_by_name(source, filterName); if (existingFilter) { return request.failed("filter name already taken"); } OBSSourceAutoRelease filter = obs_source_create_private(filterType, filterName, filterSettings); if (!filter) { return request.failed("filter creation failed"); } if (obs_source_get_type(filter) != OBS_SOURCE_TYPE_FILTER) { return request.failed("invalid filter type"); } obs_source_filter_add(source, filter); return request.success(); } /** * Remove a filter from a source * * @param {String} `sourceName` Name of the source from which the specified filter is removed * @param {String} `filterName` Name of the filter to remove * * @api requests * @name RemoveFilterFromSource * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::RemoveFilterFromSource(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("filterName")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); const char* filterName = obs_data_get_string(request.parameters(), "filterName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } OBSSourceAutoRelease filter = obs_source_get_filter_by_name(source, filterName); if (!filter) { return request.failed("specified filter doesn't exist"); } obs_source_filter_remove(source, filter); return request.success(); } /** * Move a filter in the chain (absolute index positioning) * * @param {String} `sourceName` Name of the source to which the filter belongs * @param {String} `filterName` Name of the filter to reorder * @param {Integer} `newIndex` Desired position of the filter in the chain * * @api requests * @name ReorderSourceFilter * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::ReorderSourceFilter(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("filterName") || !request.hasField("newIndex")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); const char* filterName = obs_data_get_string(request.parameters(), "filterName"); int newIndex = obs_data_get_int(request.parameters(), "newIndex"); if (newIndex < 0) { return request.failed("invalid index"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } OBSSourceAutoRelease filter = obs_source_get_filter_by_name(source, filterName); if (!filter) { return request.failed("specified filter doesn't exist"); } struct filterSearch { int i; int filterIndex; obs_source_t* filter; }; struct filterSearch ctx = { 0, 0, filter }; obs_source_enum_filters(source, [](obs_source_t *parent, obs_source_t *child, void *param) { struct filterSearch* ctx = (struct filterSearch*)param; if (child == ctx->filter) { ctx->filterIndex = ctx->i; } ctx->i++; }, &ctx); int lastFilterIndex = ctx.i + 1; if (newIndex > lastFilterIndex) { return request.failed("index out of bounds"); } int currentIndex = ctx.filterIndex; if (newIndex > currentIndex) { int downSteps = newIndex - currentIndex; for (int i = 0; i < downSteps; i++) { obs_source_filter_set_order(source, filter, OBS_ORDER_MOVE_DOWN); } } else if (newIndex < currentIndex) { int upSteps = currentIndex - newIndex; for (int i = 0; i < upSteps; i++) { obs_source_filter_set_order(source, filter, OBS_ORDER_MOVE_UP); } } return request.success(); } /** * Move a filter in the chain (relative positioning) * * @param {String} `sourceName` Name of the source to which the filter belongs * @param {String} `filterName` Name of the filter to reorder * @param {String} `movementType` How to move the filter around in the source's filter chain. Either "up", "down", "top" or "bottom". * * @api requests * @name MoveSourceFilter * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::MoveSourceFilter(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("filterName") || !request.hasField("movementType")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); const char* filterName = obs_data_get_string(request.parameters(), "filterName"); QString movementType(obs_data_get_string(request.parameters(), "movementType")); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } OBSSourceAutoRelease filter = obs_source_get_filter_by_name(source, filterName); if (!filter) { return request.failed("specified filter doesn't exist"); } obs_order_movement movement; if (movementType == "up") { movement = OBS_ORDER_MOVE_UP; } else if (movementType == "down") { movement = OBS_ORDER_MOVE_DOWN; } else if (movementType == "top") { movement = OBS_ORDER_MOVE_TOP; } else if (movementType == "bottom") { movement = OBS_ORDER_MOVE_BOTTOM; } else { return request.failed("invalid value for movementType: must be either 'up', 'down', 'top' or 'bottom'."); } obs_source_filter_set_order(source, filter, movement); return request.success(); } /** * Update settings of a filter * * @param {String} `sourceName` Name of the source to which the filter belongs * @param {String} `filterName` Name of the filter to reconfigure * @param {Object} `filterSettings` New settings. These will be merged to the current filter settings. * * @api requests * @name SetSourceFilterSettings * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::SetSourceFilterSettings(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("filterName") || !request.hasField("filterSettings")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); const char* filterName = obs_data_get_string(request.parameters(), "filterName"); OBSDataAutoRelease newFilterSettings = obs_data_get_obj(request.parameters(), "filterSettings"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } OBSSourceAutoRelease filter = obs_source_get_filter_by_name(source, filterName); if (!filter) { return request.failed("specified filter doesn't exist"); } OBSDataAutoRelease settings = obs_source_get_settings(filter); obs_data_apply(settings, newFilterSettings); obs_source_update(filter, settings); return request.success(); } /** * Change the visibility/enabled state of a filter * * @param {String} `sourceName` Source name * @param {String} `filterName` Source filter name * @param {Boolean} `filterEnabled` New filter state * * @api requests * @name SetSourceFilterVisibility * @category sources * @since 4.7.0 */ RpcResponse WSRequestHandler::SetSourceFilterVisibility(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("filterName") || !request.hasField("filterEnabled")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } const char* filterName = obs_data_get_string(request.parameters(), "filterName"); OBSSourceAutoRelease filter = obs_source_get_filter_by_name(source, filterName); if (!filter) { return request.failed("specified filter doesn't exist on specified source"); } bool filterEnabled = obs_data_get_bool(request.parameters(), "filterEnabled"); obs_source_set_enabled(filter, filterEnabled); return request.success(); } /** * Get the audio monitoring type of the specified source. * * @param {String} `sourceName` Source name. * * @return {String} `monitorType` The monitor type in use. Options: `none`, `monitorOnly`, `monitorAndOutput`. * * @api requests * @name GetAudioMonitorType * @category sources * @since 4.8.0 */ RpcResponse WSRequestHandler::GetAudioMonitorType(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(); QString monitorType; enum obs_monitoring_type mtype = obs_source_get_monitoring_type(source); switch (mtype) { case OBS_MONITORING_TYPE_NONE: monitorType = "none"; break; case OBS_MONITORING_TYPE_MONITOR_ONLY: monitorType = "monitorOnly"; break; case OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT: monitorType = "monitorAndOutput"; break; default: monitorType = "unknown"; break; } obs_data_set_string(response, "monitorType", monitorType.toUtf8()); return request.success(response); } /** * Set the audio monitoring type of the specified source. * * @param {String} `sourceName` Source name. * @param {String} `monitorType` The monitor type to use. Options: `none`, `monitorOnly`, `monitorAndOutput`. * * @api requests * @name SetAudioMonitorType * @category sources * @since 4.8.0 */ RpcResponse WSRequestHandler::SetAudioMonitorType(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("monitorType")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); QString monitorType = obs_data_get_string(request.parameters(), "monitorType"); if (sourceName.isEmpty() || monitorType.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"); } if (monitorType == "none") { obs_source_set_monitoring_type(source, OBS_MONITORING_TYPE_NONE); } else if (monitorType == "monitorOnly") { obs_source_set_monitoring_type(source, OBS_MONITORING_TYPE_MONITOR_ONLY); } else if (monitorType == "monitorAndOutput") { obs_source_set_monitoring_type(source, OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT); } else { return request.failed("invalid monitorType"); } return request.success(); } /** * Get the default settings for a given source type. * * @param {String} `sourceKind` Source kind. Also called "source id" in libobs terminology. * * @return {String} `sourceKind` Source kind. Same value as the `sourceKind` parameter. * @return {Object} `defaultSettings` Settings object for source. * * @api requests * @name GetSourceDefaultSettings * @category sources * @since 4.9.0 */ RpcResponse WSRequestHandler::GetSourceDefaultSettings(const RpcRequest& request) { if (!request.hasField("sourceKind")) { return request.failed("missing request parameters"); } QString sourceKind = obs_data_get_string(request.parameters(), "sourceKind"); if (sourceKind.isEmpty()) { return request.failed("invalid request parameters"); } OBSDataAutoRelease defaultData = obs_get_source_defaults(sourceKind.toUtf8()); if (!defaultData) { return request.failed("invalid sourceKind"); } OBSDataAutoRelease defaultSettings = Utils::OBSDataGetDefaults(defaultData); OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "sourceKind", sourceKind.toUtf8().constData()); obs_data_set_obj(response, "defaultSettings", defaultSettings); return request.success(response); } /** * Takes a picture snapshot of a source and then can either or both: * - Send it over as a Data URI (base64-encoded data) in the response (by specifying `embedPictureFormat` in the request) * - Save it to disk (by specifying `saveToFilePath` in the request) * * At least `embedPictureFormat` or `saveToFilePath` must be specified. * * Clients can specify `width` and `height` parameters to receive scaled pictures. Aspect ratio is * preserved if only one of these two parameters is specified. * * @param {String (optional)} `sourceName` Source name. Note: Since scenes are also sources, you can also provide a scene name. If not provided, the currently active scene is used. * @param {String (optional)} `embedPictureFormat` Format of the Data URI encoded picture. Can be "png", "jpg", "jpeg" or "bmp" (or any other value supported by Qt's Image module) * @param {String (optional)} `saveToFilePath` Full file path (file extension included) where the captured image is to be saved. Can be in a format different from `pictureFormat`. Can be a relative path. * @param {String (optional)} `fileFormat` Format to save the image file as (one of the values provided in the `supported-image-export-formats` response field of `GetVersion`). If not specified, tries to guess based on file extension. * @param {int (optional)} `compressionQuality` Compression ratio between -1 and 100 to write the image with. -1 is automatic, 1 is smallest file/most compression, 100 is largest file/least compression. Varies with image type. * @param {int (optional)} `width` Screenshot width. Defaults to the source's base width. * @param {int (optional)} `height` Screenshot height. Defaults to the source's base height. * * @return {String} `sourceName` Source name * @return {String} `img` Image Data URI (if `embedPictureFormat` was specified in the request) * @return {String} `imageFile` Absolute path to the saved image file (if `saveToFilePath` was specified in the request) * * @api requests * @name TakeSourceScreenshot * @category sources * @since 4.6.0 */ RpcResponse WSRequestHandler::TakeSourceScreenshot(const RpcRequest& request) { if (!request.hasField("embedPictureFormat") && !request.hasField("saveToFilePath")) { return request.failed("At least 'embedPictureFormat' or 'saveToFilePath' must be specified"); } OBSSourceAutoRelease source; if (!request.hasField("sourceName")) { source = obs_frontend_get_current_scene(); } else { const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist");; } } const uint32_t sourceWidth = obs_source_get_base_width(source); const uint32_t sourceHeight = obs_source_get_base_height(source); const double sourceAspectRatio = ((double)sourceWidth / (double)sourceHeight); uint32_t imgWidth = sourceWidth; uint32_t imgHeight = sourceHeight; if (request.hasField("width")) { imgWidth = obs_data_get_int(request.parameters(), "width"); if (!request.hasField("height")) { imgHeight = ((double)imgWidth / sourceAspectRatio); } } if (request.hasField("height")) { imgHeight = obs_data_get_int(request.parameters(), "height"); if (!request.hasField("width")) { imgWidth = ((double)imgHeight * sourceAspectRatio); } } QImage sourceImage(imgWidth, imgHeight, QImage::Format::Format_RGBA8888); sourceImage.fill(0); uint8_t* videoData = nullptr; uint32_t videoLinesize = 0; obs_enter_graphics(); gs_texrender_t* texrender = gs_texrender_create(GS_RGBA, GS_ZS_NONE); gs_stagesurf_t* stagesurface = gs_stagesurface_create(imgWidth, imgHeight, GS_RGBA); bool renderSuccess = false; gs_texrender_reset(texrender); if (gs_texrender_begin(texrender, imgWidth, imgHeight)) { vec4 background; vec4_zero(&background); gs_clear(GS_CLEAR_COLOR, &background, 0.0f, 0); gs_ortho(0.0f, (float)sourceWidth, 0.0f, (float)sourceHeight, -100.0f, 100.0f); gs_blend_state_push(); gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); obs_source_inc_showing(source); obs_source_video_render(source); obs_source_dec_showing(source); gs_blend_state_pop(); gs_texrender_end(texrender); gs_stage_texture(stagesurface, gs_texrender_get_texture(texrender)); if (gs_stagesurface_map(stagesurface, &videoData, &videoLinesize)) { int linesize = sourceImage.bytesPerLine(); for (uint y = 0; y < imgHeight; y++) { memcpy(sourceImage.scanLine(y), videoData + (y * videoLinesize), linesize); } gs_stagesurface_unmap(stagesurface); renderSuccess = true; } } gs_stagesurface_destroy(stagesurface); gs_texrender_destroy(texrender); obs_leave_graphics(); if (!renderSuccess) { return request.failed("Source render failed"); } OBSDataAutoRelease response = obs_data_create(); int compressionQuality {-1}; if (request.hasField("compressionQuality")) { compressionQuality = obs_data_get_int(request.parameters(), "compressionQuality"); if (compressionQuality < -1 || compressionQuality > 100) { QString errorMessage = QString("compression quality out of range: %1").arg(compressionQuality); return request.failed(errorMessage.toUtf8()); } } if (request.hasField("embedPictureFormat")) { const char* pictureFormat = obs_data_get_string(request.parameters(), "embedPictureFormat"); QByteArrayList supportedFormats = QImageWriter::supportedImageFormats(); if (!supportedFormats.contains(pictureFormat)) { QString errorMessage = QString("unsupported picture format: %1").arg(pictureFormat); return request.failed(errorMessage.toUtf8()); } QByteArray encodedImgBytes; QBuffer buffer(&encodedImgBytes); buffer.open(QBuffer::WriteOnly); if (!sourceImage.save(&buffer, pictureFormat, compressionQuality)) { return request.failed("embed image encoding failed"); } buffer.close(); QString imgBase64(encodedImgBytes.toBase64()); imgBase64.prepend( QString("data:image/%1;base64,").arg(pictureFormat) ); obs_data_set_string(response, "img", imgBase64.toUtf8()); } if (request.hasField("saveToFilePath")) { QString filePathStr = obs_data_get_string(request.parameters(), "saveToFilePath"); QFileInfo filePathInfo(filePathStr); QString absoluteFilePath = filePathInfo.absoluteFilePath(); const char* fileFormat = nullptr; if (request.hasField("fileFormat")) { fileFormat = obs_data_get_string(request.parameters(), "fileFormat"); QByteArrayList supportedFormats = QImageWriter::supportedImageFormats(); if (!supportedFormats.contains(fileFormat)) { QString errorMessage = QString("unsupported file format: %1").arg(fileFormat); return request.failed(errorMessage.toUtf8()); } } if (!sourceImage.save(absoluteFilePath, fileFormat, compressionQuality)) { return request.failed("Image save failed"); } obs_data_set_string(response, "imageFile", absoluteFilePath.toUtf8()); } obs_data_set_string(response, "sourceName", obs_source_get_name(source)); return request.success(response); } /** * Refreshes the specified browser source. * * @param {String} `sourceName` Source name. * * @api requests * @name RefreshBrowserSource * @category sources * @since 4.9.0 */ RpcResponse WSRequestHandler::RefreshBrowserSource(const RpcRequest& request) { if (!request.hasField("sourceName")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { return request.failed("specified source doesn't exist"); } if (strcmp(obs_source_get_id(source), "browser_source") != 0) { return request.failed("specified source is not a browser"); } obs_properties_t *sourceProperties = obs_source_properties(source); obs_property_t *property = obs_properties_get(sourceProperties, "refreshnocache"); obs_property_button_clicked(property, source); // This returns a boolean but we ignore it because the browser plugin always returns `false`. obs_properties_destroy(sourceProperties); return request.success(); } obs-websocket-4.9.0/src/WSRequestHandler_Streaming.cpp000066400000000000000000000274561401107467600230230ustar00rootroot00000000000000#include "obs-websocket.h" #include "Utils.h" #include "WSEvents.h" #include "WSRequestHandler.h" #define STREAM_SERVICE_ID "websocket_custom_service" /** * Get current streaming and recording status. * * @return {boolean} `streaming` Current streaming status. * @return {boolean} `recording` Current recording status. * @return {boolean} `recording-paused` If recording is paused. * @return {boolean} `preview-only` Always false. Retrocompatibility with OBSRemote. * @return {String (optional)} `stream-timecode` Time elapsed since streaming started (only present if currently streaming). * @return {String (optional)} `rec-timecode` Time elapsed since recording started (only present if currently recording). * * @api requests * @name GetStreamingStatus * @category streaming * @since 0.3 */ RpcResponse WSRequestHandler::GetStreamingStatus(const RpcRequest& request) { auto events = GetEventsSystem(); OBSDataAutoRelease data = obs_data_create(); obs_data_set_bool(data, "streaming", obs_frontend_streaming_active()); obs_data_set_bool(data, "recording", obs_frontend_recording_active()); obs_data_set_bool(data, "recording-paused", obs_frontend_recording_paused()); obs_data_set_bool(data, "preview-only", false); if (obs_frontend_streaming_active()) { QString streamingTimecode = events->getStreamingTimecode(); obs_data_set_string(data, "stream-timecode", streamingTimecode.toUtf8().constData()); } if (obs_frontend_recording_active()) { QString recordingTimecode = events->getRecordingTimecode(); obs_data_set_string(data, "rec-timecode", recordingTimecode.toUtf8().constData()); } return request.success(data); } /** * Toggle streaming on or off (depending on the current stream state). * * @api requests * @name StartStopStreaming * @category streaming * @since 0.3 */ RpcResponse WSRequestHandler::StartStopStreaming(const RpcRequest& request) { if (obs_frontend_streaming_active()) return StopStreaming(request); else return StartStreaming(request); } /** * Start streaming. * Will return an `error` if streaming is already active. * * @param {Object (optional)} `stream` Special stream configuration. Note: these won't be saved to OBS' configuration. * @param {String (optional)} `stream.type` If specified ensures the type of stream matches the given type (usually 'rtmp_custom' or 'rtmp_common'). If the currently configured stream type does not match the given stream type, all settings must be specified in the `settings` object or an error will occur when starting the stream. * @param {Object (optional)} `stream.metadata` Adds the given object parameters as encoded query string parameters to the 'key' of the RTMP stream. Used to pass data to the RTMP service about the streaming. May be any String, Numeric, or Boolean field. * @param {Object (optional)} `stream.settings` Settings for the stream. * @param {String (optional)} `stream.settings.server` The publish URL. * @param {String (optional)} `stream.settings.key` The publish key of the stream. * @param {boolean (optional)} `stream.settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server. * @param {String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`. * @param {String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`. * * @api requests * @name StartStreaming * @category streaming * @since 4.1.0 */ RpcResponse WSRequestHandler::StartStreaming(const RpcRequest& request) { if (obs_frontend_streaming_active() == false) { OBSService configuredService = obs_frontend_get_streaming_service(); OBSService newService = nullptr; // TODO: fix service memory leak if (request.hasField("stream")) { OBSDataAutoRelease streamData = obs_data_get_obj(request.parameters(), "stream"); OBSDataAutoRelease newSettings = obs_data_get_obj(streamData, "settings"); OBSDataAutoRelease newMetadata = obs_data_get_obj(streamData, "metadata"); OBSDataAutoRelease csHotkeys = obs_hotkeys_save_service(configuredService); QString currentType = obs_service_get_type(configuredService); QString newType = obs_data_get_string(streamData, "type"); if (newType.isEmpty() || newType.isNull()) { newType = currentType; } //Supporting adding metadata parameters to key query string QString query = Utils::ParseDataToQueryString(newMetadata); if (!query.isEmpty() && obs_data_has_user_value(newSettings, "key")) { const char* key = obs_data_get_string(newSettings, "key"); size_t keylen = strlen(key); bool hasQuestionMark = false; for (size_t i = 0; i < keylen; i++) { if (key[i] == '?') { hasQuestionMark = true; break; } } if (hasQuestionMark) { query.prepend('&'); } else { query.prepend('?'); } query.prepend(key); obs_data_set_string(newSettings, "key", query.toUtf8()); } if (newType == currentType) { // Service type doesn't change: apply settings to current service // By doing this, you can send a request to the websocket // that only contains settings you want to change, instead of // having to do a get and then change them OBSDataAutoRelease currentSettings = obs_service_get_settings(configuredService); OBSDataAutoRelease updatedSettings = obs_data_create(); obs_data_apply(updatedSettings, currentSettings); //first apply the existing settings obs_data_apply(updatedSettings, newSettings); //then apply the settings from the request should they exist newService = obs_service_create( newType.toUtf8(), STREAM_SERVICE_ID, updatedSettings, csHotkeys); } else { // Service type changed: override service settings newService = obs_service_create( newType.toUtf8(), STREAM_SERVICE_ID, newSettings, csHotkeys); } obs_frontend_set_streaming_service(newService); } obs_frontend_streaming_start(); // Stream settings provided in StartStreaming are not persisted to disk if (newService != nullptr) { obs_frontend_set_streaming_service(configuredService); } return request.success(); } else { return request.failed("streaming already active"); } } /** * Stop streaming. * Will return an `error` if streaming is not active. * * @api requests * @name StopStreaming * @category streaming * @since 4.1.0 */ RpcResponse WSRequestHandler::StopStreaming(const RpcRequest& request) { if (obs_frontend_streaming_active() == true) { obs_frontend_streaming_stop(); return request.success(); } else { return request.failed("streaming not active"); } } /** * Sets one or more attributes of the current streaming server settings. Any options not passed will remain unchanged. Returns the updated settings in response. If 'type' is different than the current streaming service type, all settings are required. Returns the full settings of the stream (the same as GetStreamSettings). * * @param {String} `type` The type of streaming service configuration, usually `rtmp_custom` or `rtmp_common`. * @param {Object} `settings` The actual settings of the stream. * @param {String (optional)} `settings.server` The publish URL. * @param {String (optional)} `settings.key` The publish key. * @param {boolean (optional)} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server. * @param {String (optional)} `settings.username` The username for the streaming service. * @param {String (optional)} `settings.password` The password for the streaming service. * @param {boolean} `save` Persist the settings to disk. * * @api requests * @name SetStreamSettings * @category streaming * @since 4.1.0 */ RpcResponse WSRequestHandler::SetStreamSettings(const RpcRequest& request) { OBSService service = obs_frontend_get_streaming_service(); OBSDataAutoRelease requestSettings = obs_data_get_obj(request.parameters(), "settings"); if (!requestSettings) { return request.failed("'settings' are required'"); } QString serviceType = obs_service_get_type(service); QString requestedType = obs_data_get_string(request.parameters(), "type"); if (requestedType != nullptr && requestedType != serviceType) { OBSDataAutoRelease hotkeys = obs_hotkeys_save_service(service); service = obs_service_create( requestedType.toUtf8(), STREAM_SERVICE_ID, requestSettings, hotkeys); obs_frontend_set_streaming_service(service); } else { // If type isn't changing, we should overlay the settings we got // to the existing settings. By doing so, you can send a request that // only contains the settings you want to change, instead of having to // do a get and then change them OBSDataAutoRelease existingSettings = obs_service_get_settings(service); OBSDataAutoRelease newSettings = obs_data_create(); // Apply existing settings obs_data_apply(newSettings, existingSettings); // Then apply the settings from the request obs_data_apply(newSettings, requestSettings); obs_service_update(service, newSettings); } //if save is specified we should immediately save the streaming service if (obs_data_get_bool(request.parameters(), "save")) { obs_frontend_save_streaming_service(); } OBSService responseService = obs_frontend_get_streaming_service(); OBSDataAutoRelease serviceSettings = obs_service_get_settings(responseService); const char* responseType = obs_service_get_type(responseService); OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "type", responseType); obs_data_set_obj(response, "settings", serviceSettings); return request.success(response); } /** * Get the current streaming server settings. * * @return {String} `type` The type of streaming service configuration. Possible values: 'rtmp_custom' or 'rtmp_common'. * @return {Object} `settings` Stream settings object. * @return {String} `settings.server` The publish URL. * @return {String} `settings.key` The publish key of the stream. * @return {boolean} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server. * @return {String} `settings.username` The username to use when accessing the streaming server. Only present if `use_auth` is `true`. * @return {String} `settings.password` The password to use when accessing the streaming server. Only present if `use_auth` is `true`. * * @api requests * @name GetStreamSettings * @category streaming * @since 4.1.0 */ RpcResponse WSRequestHandler::GetStreamSettings(const RpcRequest& request) { OBSService service = obs_frontend_get_streaming_service(); const char* serviceType = obs_service_get_type(service); OBSDataAutoRelease settings = obs_service_get_settings(service); OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "type", serviceType); obs_data_set_obj(response, "settings", settings); return request.success(response); } /** * Save the current streaming server settings to disk. * * @api requests * @name SaveStreamSettings * @category streaming * @since 4.1.0 */ RpcResponse WSRequestHandler::SaveStreamSettings(const RpcRequest& request) { obs_frontend_save_streaming_service(); return request.success(); } /** * Send the provided text as embedded CEA-608 caption data. * * @param {String} `text` Captions text * * @api requests * @name SendCaptions * @category streaming * @since 4.6.0 */ RpcResponse WSRequestHandler::SendCaptions(const RpcRequest& request) { if (!request.hasField("text")) { return request.failed("missing request parameters"); } OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); if (output) { const char* caption = obs_data_get_string(request.parameters(), "text"); // Send caption text with immediately (0 second delay) obs_output_output_caption_text2(output, caption, 0.0); } return request.success(); } obs-websocket-4.9.0/src/WSRequestHandler_StudioMode.cpp000066400000000000000000000122201401107467600231250ustar00rootroot00000000000000#include "Utils.h" #include "WSRequestHandler.h" /** * Indicates if Studio Mode is currently enabled. * * @return {boolean} `studio-mode` Indicates if Studio Mode is enabled. * * @api requests * @name GetStudioModeStatus * @category studio mode * @since 4.1.0 */ RpcResponse WSRequestHandler::GetStudioModeStatus(const RpcRequest& request) { bool previewActive = obs_frontend_preview_program_mode_active(); OBSDataAutoRelease response = obs_data_create(); obs_data_set_bool(response, "studio-mode", previewActive); return request.success(response); } /** * Get the name of the currently previewed scene and its list of sources. * Will return an `error` if Studio Mode is not enabled. * * @return {String} `name` The name of the active preview scene. * @return {Array} `sources` * * @api requests * @name GetPreviewScene * @category studio mode * @since 4.1.0 */ RpcResponse WSRequestHandler::GetPreviewScene(const RpcRequest& request) { if (!obs_frontend_preview_program_mode_active()) { return request.failed("studio mode not enabled"); } OBSSourceAutoRelease scene = obs_frontend_get_current_preview_scene(); OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(scene); OBSDataAutoRelease data = obs_data_create(); obs_data_set_string(data, "name", obs_source_get_name(scene)); obs_data_set_array(data, "sources", sceneItems); return request.success(data); } /** * Set the active preview scene. * Will return an `error` if Studio Mode is not enabled. * * @param {String} `scene-name` The name of the scene to preview. * * @api requests * @name SetPreviewScene * @category studio mode * @since 4.1.0 */ RpcResponse WSRequestHandler::SetPreviewScene(const RpcRequest& request) { if (!obs_frontend_preview_program_mode_active()) { return request.failed("studio mode not enabled"); } if (!request.hasField("scene-name")) { return request.failed("missing request parameters"); } const char* scene_name = obs_data_get_string(request.parameters(), "scene-name"); OBSScene scene = Utils::GetSceneFromNameOrCurrent(scene_name); if (!scene) { return request.failed("specified scene doesn't exist"); } obs_frontend_set_current_preview_scene(obs_scene_get_source(scene)); return request.success(); } /** * Transitions the currently previewed scene to the main output. * Will return an `error` if Studio Mode is not enabled. * * @param {Object (optional)} `with-transition` Change the active transition before switching scenes. Defaults to the active transition. * @param {String} `with-transition.name` Name of the transition. * @param {int (optional)} `with-transition.duration` Transition duration (in milliseconds). * * @api requests * @name TransitionToProgram * @category studio mode * @since 4.1.0 */ RpcResponse WSRequestHandler::TransitionToProgram(const RpcRequest& request) { if (!obs_frontend_preview_program_mode_active()) { return request.failed("studio mode not enabled"); } if (request.hasField("with-transition")) { OBSDataAutoRelease transitionInfo = obs_data_get_obj(request.parameters(), "with-transition"); if (obs_data_has_user_value(transitionInfo, "name")) { QString transitionName = obs_data_get_string(transitionInfo, "name"); if (transitionName.isEmpty()) { return request.failed("invalid request parameters"); } bool success = Utils::SetTransitionByName(transitionName); if (!success) { return request.failed("specified transition doesn't exist"); } } if (obs_data_has_user_value(transitionInfo, "duration")) { int transitionDuration = obs_data_get_int(transitionInfo, "duration"); obs_frontend_set_transition_duration(transitionDuration); } } obs_frontend_preview_program_trigger_transition(); return request.success(); } /** * Enables Studio Mode. * * @api requests * @name EnableStudioMode * @category studio mode * @since 4.1.0 */ RpcResponse WSRequestHandler::EnableStudioMode(const RpcRequest& request) { if (obs_frontend_preview_program_mode_active()) { return request.failed("studio mode already active"); } obs_queue_task(OBS_TASK_UI, [](void* param) { obs_frontend_set_preview_program_mode(true); UNUSED_PARAMETER(param); }, nullptr, true); return request.success(); } /** * Disables Studio Mode. * * @api requests * @name DisableStudioMode * @category studio mode * @since 4.1.0 */ RpcResponse WSRequestHandler::DisableStudioMode(const RpcRequest& request) { if (!obs_frontend_preview_program_mode_active()) { return request.failed("studio mode not active"); } obs_queue_task(OBS_TASK_UI, [](void* param) { obs_frontend_set_preview_program_mode(false); UNUSED_PARAMETER(param); }, nullptr, true); return request.success(); } /** * Toggles Studio Mode (depending on the current state of studio mode). * * @api requests * @name ToggleStudioMode * @category studio mode * @since 4.1.0 */ RpcResponse WSRequestHandler::ToggleStudioMode(const RpcRequest& request) { obs_queue_task(OBS_TASK_UI, [](void* param) { bool previewProgramMode = obs_frontend_preview_program_mode_active(); obs_frontend_set_preview_program_mode(!previewProgramMode); UNUSED_PARAMETER(param); }, nullptr, true); return request.success(); }obs-websocket-4.9.0/src/WSRequestHandler_Transitions.cpp000066400000000000000000000216751401107467600234040ustar00rootroot00000000000000#include "Utils.h" #include "WSRequestHandler.h" /** * List of all transitions available in the frontend's dropdown menu. * * @return {String} `current-transition` Name of the currently active transition. * @return {Array} `transitions` List of transitions. * @return {String} `transitions.*.name` Name of the transition. * * @api requests * @name GetTransitionList * @category transitions * @since 4.1.0 */ RpcResponse WSRequestHandler::GetTransitionList(const RpcRequest& request) { OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition(); obs_frontend_source_list transitionList = {}; obs_frontend_get_transitions(&transitionList); OBSDataArrayAutoRelease transitions = obs_data_array_create(); for (size_t i = 0; i < transitionList.sources.num; i++) { OBSSource transition = transitionList.sources.array[i]; OBSDataAutoRelease obj = obs_data_create(); obs_data_set_string(obj, "name", obs_source_get_name(transition)); obs_data_array_push_back(transitions, obj); } obs_frontend_source_list_free(&transitionList); OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "current-transition", obs_source_get_name(currentTransition)); obs_data_set_array(response, "transitions", transitions); return request.success(response); } /** * Get the name of the currently selected transition in the frontend's dropdown menu. * * @return {String} `name` Name of the selected transition. * @return {int (optional)} `duration` Transition duration (in milliseconds) if supported by the transition. * * @api requests * @name GetCurrentTransition * @category transitions * @since 0.3 */ RpcResponse WSRequestHandler::GetCurrentTransition(const RpcRequest& request) { OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition(); OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "name", obs_source_get_name(currentTransition)); if (!obs_transition_fixed(currentTransition)) obs_data_set_int(response, "duration", obs_frontend_get_transition_duration()); return request.success(response); } /** * Set the active transition. * * @param {String} `transition-name` The name of the transition. * * @api requests * @name SetCurrentTransition * @category transitions * @since 0.3 */ RpcResponse WSRequestHandler::SetCurrentTransition(const RpcRequest& request) { if (!request.hasField("transition-name")) { return request.failed("missing request parameters"); } QString name = obs_data_get_string(request.parameters(), "transition-name"); bool success = Utils::SetTransitionByName(name); if (!success) { return request.failed("requested transition does not exist"); } return request.success(); } /** * Set the duration of the currently selected transition if supported. * * @param {int} `duration` Desired duration of the transition (in milliseconds). * * @api requests * @name SetTransitionDuration * @category transitions * @since 4.0.0 */ RpcResponse WSRequestHandler::SetTransitionDuration(const RpcRequest& request) { if (!request.hasField("duration")) { return request.failed("missing request parameters"); } int ms = obs_data_get_int(request.parameters(), "duration"); obs_frontend_set_transition_duration(ms); return request.success(); } /** * Get the duration of the currently selected transition if supported. * * @return {int} `transition-duration` Duration of the current transition (in milliseconds). * * @api requests * @name GetTransitionDuration * @category transitions * @since 4.1.0 */ RpcResponse WSRequestHandler::GetTransitionDuration(const RpcRequest& request) { OBSDataAutoRelease response = obs_data_create(); obs_data_set_int(response, "transition-duration", obs_frontend_get_transition_duration()); return request.success(response); } /** * Get the position of the current transition. * * @return {double} `position` current transition position. This value will be between 0.0 and 1.0. Note: Transition returns 1.0 when not active. * * @api requests * @name GetTransitionPosition * @category transitions * @since 4.9.0 */ RpcResponse WSRequestHandler::GetTransitionPosition(const RpcRequest& request) { OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition(); OBSDataAutoRelease response = obs_data_create(); obs_data_set_double(response, "position", obs_transition_get_time(currentTransition)); return request.success(response); } /** * Get the current settings of a transition * * @param {String} `transitionName` Transition name * * @return {Object} `transitionSettings` Current transition settings * * @api requests * @name GetTransitionSettings * @category transitions * @since 4.9.0 */ RpcResponse WSRequestHandler::GetTransitionSettings(const RpcRequest& request) { if (!request.hasField("transitionName")) { return request.failed("missing request parameters"); } const char* transitionName = obs_data_get_string(request.parameters(), "transitionName"); OBSSourceAutoRelease transition = Utils::GetTransitionFromName(transitionName); if (!transition) { return request.failed("specified transition doesn't exist"); } OBSDataAutoRelease transitionSettings = obs_source_get_settings(transition); OBSDataAutoRelease response = obs_data_create(); obs_data_set_obj(response, "transitionSettings", transitionSettings); return request.success(response); } /** * Change the current settings of a transition * * @param {String} `transitionName` Transition name * @param {Object} `transitionSettings` Transition settings (they can be partial) * * @return {Object} `transitionSettings` Updated transition settings * * @api requests * @name SetTransitionSettings * @category transitions * @since 4.9.0 */ RpcResponse WSRequestHandler::SetTransitionSettings(const RpcRequest& request) { if (!request.hasField("transitionName") || !request.hasField("transitionSettings")) { return request.failed("missing request parameters"); } const char* transitionName = obs_data_get_string(request.parameters(), "transitionName"); OBSSourceAutoRelease transition = Utils::GetTransitionFromName(transitionName); if (!transition) { return request.failed("specified transition doesn't exist"); } OBSDataAutoRelease newSettings = obs_data_get_obj(request.parameters(), "transitionSettings"); obs_source_update(transition, newSettings); obs_source_update_properties(transition); OBSDataAutoRelease updatedSettings = obs_source_get_settings(transition); OBSDataAutoRelease response = obs_data_create(); obs_data_set_obj(response, "transitionSettings", updatedSettings); return request.success(response); } /** * Release the T-Bar (like a user releasing their mouse button after moving it). * *YOU MUST CALL THIS if you called `SetTBarPosition` with the `release` parameter set to `false`.* * * @api requests * @name ReleaseTBar * @category transitions * @since 4.9.0 */ RpcResponse WSRequestHandler::ReleaseTBar(const RpcRequest& request) { if (!obs_frontend_preview_program_mode_active()) { return request.failed("studio mode not enabled"); } if (obs_transition_fixed(obs_frontend_get_current_transition())) { return request.failed("current transition doesn't support t-bar control"); } obs_frontend_release_tbar(); return request.success(); } /** * Set the manual position of the T-Bar (in Studio Mode) to the specified value. Will return an error if OBS is not in studio mode * or if the current transition doesn't support T-Bar control. * * If your code needs to perform multiple successive T-Bar moves (e.g. : in an animation, or in response to a user moving a T-Bar control in your User Interface), set `release` to false and call `ReleaseTBar` later once the animation/interaction is over. * * @param {double} `position` T-Bar position. This value must be between 0.0 and 1.0. * @param {boolean (optional)} `release` Whether or not the T-Bar gets released automatically after setting its new position (like a user releasing their mouse button after moving the T-Bar). Call `ReleaseTBar` manually if you set `release` to false. Defaults to true. * * @api requests * @name SetTBarPosition * @category transitions * @since 4.9.0 */ RpcResponse WSRequestHandler::SetTBarPosition(const RpcRequest& request) { if (!obs_frontend_preview_program_mode_active()) { return request.failed("studio mode not enabled"); } if (obs_transition_fixed(obs_frontend_get_current_transition())) { return request.failed("current transition doesn't support t-bar control"); } if (!request.hasField("position")) { return request.failed("missing request parameters"); } double position = obs_data_get_double(request.parameters(), "position"); if (position < 0.0 || position > 1.0) { return request.failed("position is out of range"); } bool release = true; if (request.hasField("release")) { release = obs_data_get_bool(request.parameters(), "release"); } obs_frontend_set_tbar_position((int)((float)position * 1024.0)); if (release) { obs_frontend_release_tbar(); } return request.success(); } obs-websocket-4.9.0/src/WSServer.cpp000066400000000000000000000156331401107467600173240ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2017 Stéphane Lepin 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 */ #include #include #include #include #include #include #include #include #include #include "WSServer.h" #include "obs-websocket.h" #include "Config.h" #include "Utils.h" #include "protocol/OBSRemoteProtocol.h" QT_USE_NAMESPACE using websocketpp::lib::placeholders::_1; using websocketpp::lib::placeholders::_2; using websocketpp::lib::bind; WSServer::WSServer() : QObject(nullptr), _connections(), _clMutex(QMutex::Recursive) { _server.get_alog().clear_channels(websocketpp::log::alevel::frame_header | websocketpp::log::alevel::frame_payload | websocketpp::log::alevel::control); _server.init_asio(); #ifndef _WIN32 _server.set_reuse_addr(true); #endif _server.set_open_handler(bind(&WSServer::onOpen, this, ::_1)); _server.set_close_handler(bind(&WSServer::onClose, this, ::_1)); _server.set_message_handler(bind(&WSServer::onMessage, this, ::_1, ::_2)); } WSServer::~WSServer() { stop(); } void WSServer::start(quint16 port, bool lockToIPv4) { if (_server.is_listening() && (port == _serverPort && _lockToIPv4 == lockToIPv4)) { blog(LOG_INFO, "WSServer::start: server already on this port and protocol mode. no restart needed"); return; } if (_server.is_listening()) { stop(); } _server.reset(); _serverPort = port; _lockToIPv4 = lockToIPv4; websocketpp::lib::error_code errorCode; if (lockToIPv4) { blog(LOG_INFO, "WSServer::start: Locked to IPv4 bindings"); _server.listen(websocketpp::lib::asio::ip::tcp::v4(), _serverPort, errorCode); } else { blog(LOG_INFO, "WSServer::start: Not locked to IPv4 bindings"); _server.listen(_serverPort, errorCode); } if (errorCode) { std::string errorCodeMessage = errorCode.message(); blog(LOG_INFO, "server: listen failed: %s", errorCodeMessage.c_str()); obs_frontend_push_ui_translation(obs_module_get_string); QString errorTitle = tr("OBSWebsocket.Server.StartFailed.Title"); QString errorMessage = tr("OBSWebsocket.Server.StartFailed.Message").arg(_serverPort).arg(errorCodeMessage.c_str()); obs_frontend_pop_ui_translation(); QMainWindow* mainWindow = reinterpret_cast(obs_frontend_get_main_window()); QMessageBox::warning(mainWindow, errorTitle, errorMessage); return; } _server.start_accept(); QtConcurrent::run([=]() { blog(LOG_INFO, "io thread started"); _server.run(); blog(LOG_INFO, "io thread exited"); }); blog(LOG_INFO, "server started successfully on port %d", _serverPort); } void WSServer::stop() { if (!_server.is_listening()) { return; } _server.stop_listening(); for (connection_hdl hdl : _connections) { _server.close(hdl, websocketpp::close::status::going_away, "Server stopping"); } _threadPool.waitForDone(); while (_connections.size() > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } blog(LOG_INFO, "server stopped successfully"); } void WSServer::broadcast(const RpcEvent& event) { std::string message = OBSRemoteProtocol::encodeEvent(event); if (GetConfig()->DebugEnabled) { blog(LOG_INFO, "Update << '%s'", message.c_str()); } QMutexLocker locker(&_clMutex); for (connection_hdl hdl : _connections) { if (GetConfig()->AuthRequired) { bool authenticated = _connectionProperties[hdl].isAuthenticated(); if (!authenticated) { continue; } } websocketpp::lib::error_code errorCode; _server.send(hdl, message, websocketpp::frame::opcode::text, errorCode); if (errorCode) { std::string errorCodeMessage = errorCode.message(); blog(LOG_INFO, "server(broadcast): send failed: %s", errorCodeMessage.c_str()); } } } void WSServer::onOpen(connection_hdl hdl) { QMutexLocker locker(&_clMutex); _connections.insert(hdl); locker.unlock(); QString clientIp = getRemoteEndpoint(hdl); notifyConnection(clientIp); blog(LOG_INFO, "new client connection from %s", clientIp.toUtf8().constData()); } void WSServer::onMessage(connection_hdl hdl, server::message_ptr message) { auto opcode = message->get_opcode(); if (opcode != websocketpp::frame::opcode::text) { return; } QtConcurrent::run(&_threadPool, [=]() { std::string payload = message->get_payload(); QMutexLocker locker(&_clMutex); ConnectionProperties& connProperties = _connectionProperties[hdl]; locker.unlock(); if (GetConfig()->DebugEnabled) { blog(LOG_INFO, "Request >> '%s'", payload.c_str()); } WSRequestHandler requestHandler(connProperties); std::string response = OBSRemoteProtocol::processMessage(requestHandler, payload); if (GetConfig()->DebugEnabled) { blog(LOG_INFO, "Response << '%s'", response.c_str()); } websocketpp::lib::error_code errorCode; _server.send(hdl, response, websocketpp::frame::opcode::text, errorCode); if (errorCode) { std::string errorCodeMessage = errorCode.message(); blog(LOG_INFO, "server(response): send failed: %s", errorCodeMessage.c_str()); } }); } void WSServer::onClose(connection_hdl hdl) { QMutexLocker locker(&_clMutex); _connections.erase(hdl); _connectionProperties.erase(hdl); locker.unlock(); auto conn = _server.get_con_from_hdl(hdl); auto localCloseCode = conn->get_local_close_code(); if (localCloseCode != websocketpp::close::status::going_away) { QString clientIp = getRemoteEndpoint(hdl); notifyDisconnection(clientIp); blog(LOG_INFO, "client %s disconnected", clientIp.toUtf8().constData()); } } QString WSServer::getRemoteEndpoint(connection_hdl hdl) { auto conn = _server.get_con_from_hdl(hdl); return QString::fromStdString(conn->get_remote_endpoint()); } void WSServer::notifyConnection(QString clientIp) { obs_frontend_push_ui_translation(obs_module_get_string); QString title = tr("OBSWebsocket.NotifyConnect.Title"); QString msg = tr("OBSWebsocket.NotifyConnect.Message").arg(clientIp); obs_frontend_pop_ui_translation(); Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title); } void WSServer::notifyDisconnection(QString clientIp) { obs_frontend_push_ui_translation(obs_module_get_string); QString title = tr("OBSWebsocket.NotifyDisconnect.Title"); QString msg = tr("OBSWebsocket.NotifyDisconnect.Message").arg(clientIp); obs_frontend_pop_ui_translation(); Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title); } obs-websocket-4.9.0/src/WSServer.h000066400000000000000000000036671401107467600167750ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2019 Stéphane Lepin 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 */ #pragma once #include #include #include #include #include #include #include #include #include #include "ConnectionProperties.h" #include "WSRequestHandler.h" #include "rpc/RpcEvent.h" using websocketpp::connection_hdl; typedef websocketpp::server server; class WSServer : public QObject { Q_OBJECT public: explicit WSServer(); virtual ~WSServer(); void start(quint16 port, bool lockToIPv4); void stop(); void broadcast(const RpcEvent& event); QThreadPool* threadPool() { return &_threadPool; } private: void onOpen(connection_hdl hdl); void onMessage(connection_hdl hdl, server::message_ptr message); void onClose(connection_hdl hdl); QString getRemoteEndpoint(connection_hdl hdl); void notifyConnection(QString clientIp); void notifyDisconnection(QString clientIp); server _server; quint16 _serverPort; bool _lockToIPv4; std::set> _connections; std::map> _connectionProperties; QMutex _clMutex; QThreadPool _threadPool; }; obs-websocket-4.9.0/src/forms/000077500000000000000000000000001401107467600162165ustar00rootroot00000000000000obs-websocket-4.9.0/src/forms/settings-dialog.cpp000066400000000000000000000066761401107467600220360ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2017 Stéphane Lepin 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 */ #include "settings-dialog.h" #include #include #include #include "../obs-websocket.h" #include "../Config.h" #include "../WSServer.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); } void SettingsDialog::showEvent(QShowEvent* event) { auto conf = GetConfig(); ui->serverEnabled->setChecked(conf->ServerEnabled); ui->serverPort->setValue(conf->ServerPort); ui->lockToIPv4->setChecked(conf->LockToIPv4); ui->debugEnabled->setChecked(conf->DebugEnabled); ui->alertsEnabled->setChecked(conf->AlertsEnabled); ui->authRequired->blockSignals(true); ui->authRequired->setChecked(conf->AuthRequired); ui->authRequired->blockSignals(false); ui->password->setText(CHANGE_ME); ui->password->setEnabled(ui->authRequired->isChecked()); } void SettingsDialog::ToggleShowHide() { if (!isVisible()) setVisible(true); else setVisible(false); } void SettingsDialog::PreparePasswordEntry() { ui->authRequired->blockSignals(true); ui->authRequired->setChecked(true); ui->authRequired->blockSignals(false); ui->password->setEnabled(true); ui->password->setFocus(); } void SettingsDialog::AuthCheckboxChanged() { if (ui->authRequired->isChecked()) { ui->password->setEnabled(true); } else { obs_frontend_push_ui_translation(obs_module_get_string); QString authDisabledWarning = QObject::tr("OBSWebsocket.Settings.AuthDisabledWarning"); obs_frontend_pop_ui_translation(); QMessageBox::StandardButton response = QMessageBox::question(this, "obs-websocket", authDisabledWarning); if (response == QMessageBox::Yes) { ui->password->setEnabled(false); } else { ui->authRequired->setChecked(true); } } } void SettingsDialog::FormAccepted() { auto conf = GetConfig(); conf->ServerEnabled = ui->serverEnabled->isChecked(); conf->ServerPort = ui->serverPort->value(); conf->LockToIPv4 = ui->lockToIPv4->isChecked(); conf->DebugEnabled = ui->debugEnabled->isChecked(); conf->AlertsEnabled = ui->alertsEnabled->isChecked(); if (ui->authRequired->isChecked()) { if (ui->password->text() != CHANGE_ME) { conf->SetPassword(ui->password->text()); } if (!GetConfig()->Secret.isEmpty()) conf->AuthRequired = true; else conf->AuthRequired = false; } else { conf->AuthRequired = false; } conf->Save(); auto server = GetServer(); if (conf->ServerEnabled) { server->start(conf->ServerPort, conf->LockToIPv4); } else { server->stop(); } } SettingsDialog::~SettingsDialog() { delete ui; } obs-websocket-4.9.0/src/forms/settings-dialog.h000066400000000000000000000021141401107467600214620ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2019 Stéphane Lepin 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 */ #pragma once #include #include "ui_settings-dialog.h" class SettingsDialog : public QDialog { Q_OBJECT public: explicit SettingsDialog(QWidget* parent = 0); ~SettingsDialog(); void showEvent(QShowEvent* event); void ToggleShowHide(); void PreparePasswordEntry(); private Q_SLOTS: void AuthCheckboxChanged(); void FormAccepted(); private: Ui::SettingsDialog* ui; }; obs-websocket-4.9.0/src/forms/settings-dialog.ui000066400000000000000000000102071401107467600216520ustar00rootroot00000000000000 SettingsDialog 0 0 407 216 0 0 OBSWebsocket.Settings.DialogTitle false QLayout::SetDefaultConstraint OBSWebsocket.Settings.AuthRequired OBSWebsocket.Settings.Password QLineEdit::Password OBSWebsocket.Settings.ServerEnable true OBSWebsocket.Settings.ServerPort 1024 65535 4444 OBSWebsocket.Settings.AlertsEnable true OBSWebsocket.Settings.DebugEnable false OBSWebsocket.Settings.LockToIPv4 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() SettingsDialog accept() 248 294 157 314 buttonBox rejected() SettingsDialog reject() 316 300 286 314 obs-websocket-4.9.0/src/obs-websocket.cpp000066400000000000000000000070601401107467600203460ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2017 Stéphane Lepin 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 */ #include #include #include #include #include #include #include "obs-websocket.h" #include "WSServer.h" #include "WSEvents.h" #include "Config.h" #include "forms/settings-dialog.h" void ___source_dummy_addref(obs_source_t*) {} void ___sceneitem_dummy_addref(obs_sceneitem_t*) {} void ___data_dummy_addref(obs_data_t*) {} void ___data_array_dummy_addref(obs_data_array_t*) {} void ___output_dummy_addref(obs_output_t*) {} void ___data_item_dummy_addref(obs_data_item_t*) {} void ___data_item_release(obs_data_item_t* dataItem) { obs_data_item_release(&dataItem); } OBS_DECLARE_MODULE() OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US") ConfigPtr _config; WSServerPtr _server; WSEventsPtr _eventsSystem; SettingsDialog* settingsDialog = nullptr; 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 = ConfigPtr(new Config()); _config->MigrateFromGlobalSettings(); // TODO remove this on the next minor jump _config->Load(); _server = WSServerPtr(new WSServer()); _eventsSystem = WSEventsPtr(new WSEvents(_server)); // UI setup obs_frontend_push_ui_translation(obs_module_get_string); QMainWindow* mainWindow = (QMainWindow*)obs_frontend_get_main_window(); settingsDialog = new SettingsDialog(mainWindow); obs_frontend_pop_ui_translation(); const char* menuActionText = obs_module_text("OBSWebsocket.Settings.DialogTitle"); QAction* menuAction = (QAction*)obs_frontend_add_tools_menu_qaction(menuActionText); QObject::connect(menuAction, &QAction::triggered, [] { // The settings dialog belongs to the main window. Should be ok // to pass the pointer to this QAction belonging to the main window settingsDialog->ToggleShowHide(); }); // Setup event handler to start the server once OBS is ready auto eventCallback = [](enum obs_frontend_event event, void *param) { if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) { if (_config->ServerEnabled) { _server->start(_config->ServerPort, _config->LockToIPv4); } obs_frontend_remove_event_callback((obs_frontend_event_cb)param, nullptr); } }; obs_frontend_add_event_callback(eventCallback, (void*)(obs_frontend_event_cb)eventCallback); // Loading finished blog(LOG_INFO, "module loaded!"); return true; } void obs_module_unload() { _server->stop(); _eventsSystem.reset(); _server.reset(); _config.reset(); blog(LOG_INFO, "goodbye!"); } ConfigPtr GetConfig() { return _config; } WSServerPtr GetServer() { return _server; } WSEventsPtr GetEventsSystem() { return _eventsSystem; } void ShowPasswordSetting() { if (settingsDialog) { settingsDialog->PreparePasswordEntry(); settingsDialog->setVisible(true); } } obs-websocket-4.9.0/src/obs-websocket.h000066400000000000000000000040711401107467600200120ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2019 Stéphane Lepin 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 */ #pragma once #include #include void ___source_dummy_addref(obs_source_t*); void ___sceneitem_dummy_addref(obs_sceneitem_t*); void ___data_dummy_addref(obs_data_t*); void ___data_array_dummy_addref(obs_data_array_t*); void ___output_dummy_addref(obs_output_t*); using OBSSourceAutoRelease = OBSRef; using OBSSceneItemAutoRelease = OBSRef; using OBSDataAutoRelease = OBSRef; using OBSDataArrayAutoRelease = OBSRef; using OBSOutputAutoRelease = OBSRef; void ___data_item_dummy_addref(obs_data_item_t*); void ___data_item_release(obs_data_item_t*); using OBSDataItemAutoRelease = OBSRef; class Config; typedef std::shared_ptr ConfigPtr; class WSServer; typedef std::shared_ptr WSServerPtr; class WSEvents; typedef std::shared_ptr WSEventsPtr; ConfigPtr GetConfig(); WSServerPtr GetServer(); WSEventsPtr GetEventsSystem(); void ShowPasswordSetting(); #define OBS_WEBSOCKET_VERSION "4.9.0" #define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__) obs-websocket-4.9.0/src/protocol/000077500000000000000000000000001401107467600167315ustar00rootroot00000000000000obs-websocket-4.9.0/src/protocol/OBSRemoteProtocol.cpp000066400000000000000000000106271401107467600227640ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2019 Stéphane Lepin 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 */ #include #include "OBSRemoteProtocol.h" #include "../WSRequestHandler.h" #include "../rpc/RpcEvent.h" #include "../Utils.h" std::string OBSRemoteProtocol::processMessage(WSRequestHandler& requestHandler, std::string message) { std::string msgContainer(message); const char* msg = msgContainer.c_str(); OBSDataAutoRelease data = obs_data_create_from_json(msg); if (!data) { blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg); return jsonDataToString( errorResponse(nullptr, "invalid JSON payload") ); } if (!obs_data_has_user_value(data, "request-type") || !obs_data_has_user_value(data, "message-id")) { return jsonDataToString( errorResponse(nullptr, "missing request parameters") ); } QString methodName = obs_data_get_string(data, "request-type"); QString messageId = obs_data_get_string(data, "message-id"); OBSDataAutoRelease params = obs_data_create(); obs_data_apply(params, data); obs_data_unset_user_value(params, "request-type"); obs_data_unset_user_value(params, "message-id"); RpcRequest request(messageId, methodName, params); RpcResponse response = requestHandler.processRequest(request); OBSDataAutoRelease responseData = rpcResponseToJsonData(response); return jsonDataToString(responseData); } std::string OBSRemoteProtocol::encodeEvent(const RpcEvent& event) { OBSDataAutoRelease eventData = obs_data_create(); QString updateType = event.updateType(); obs_data_set_string(eventData, "update-type", updateType.toUtf8().constData()); std::optional streamTime = event.streamTime(); if (streamTime.has_value()) { QString streamingTimecode = Utils::nsToTimestamp(streamTime.value()); obs_data_set_string(eventData, "stream-timecode", streamingTimecode.toUtf8().constData()); } std::optional recordingTime = event.recordingTime(); if (recordingTime.has_value()) { QString recordingTimecode = Utils::nsToTimestamp(recordingTime.value()); obs_data_set_string(eventData, "rec-timecode", recordingTimecode.toUtf8().constData()); } OBSData additionalFields = event.additionalFields(); if (additionalFields) { obs_data_apply(eventData, additionalFields); } return std::string(obs_data_get_json(eventData)); } obs_data_t* OBSRemoteProtocol::rpcResponseToJsonData(const RpcResponse& response) { QByteArray messageIdBytes = response.messageId().toUtf8(); const char* messageId = messageIdBytes.constData(); OBSData additionalFields = response.additionalFields(); switch (response.status()) { case RpcResponse::Status::Ok: return successResponse(messageId, additionalFields); case RpcResponse::Status::Error: return errorResponse(messageId, response.errorMessage().toUtf8().constData(), additionalFields); default: assert(false); } return nullptr; } obs_data_t* OBSRemoteProtocol::successResponse(const char* messageId, obs_data_t* fields) { return buildResponse(messageId, "ok", fields); } obs_data_t* OBSRemoteProtocol::errorResponse(const char* messageId, const char* errorMessage, obs_data_t* additionalFields) { OBSDataAutoRelease fields = obs_data_create(); if (additionalFields) { obs_data_apply(fields, additionalFields); } obs_data_set_string(fields, "error", errorMessage); return buildResponse(messageId, "error", fields); } obs_data_t* OBSRemoteProtocol::buildResponse(const char* messageId, const char* status, obs_data_t* fields) { obs_data_t* response = obs_data_create(); if (messageId) { obs_data_set_string(response, "message-id", messageId); } obs_data_set_string(response, "status", status); if (fields) { obs_data_apply(response, fields); } return response; } std::string OBSRemoteProtocol::jsonDataToString(OBSDataAutoRelease data) { std::string responseString = obs_data_get_json(data); return responseString; } obs-websocket-4.9.0/src/protocol/OBSRemoteProtocol.h000066400000000000000000000027011401107467600224230ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2019 Stéphane Lepin 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 */ #pragma once #include #include #include "../rpc/RpcResponse.h" class WSRequestHandler; class RpcEvent; class OBSRemoteProtocol { public: static std::string processMessage(WSRequestHandler& requestHandler, std::string message); static std::string encodeEvent(const RpcEvent& event); static obs_data_t* rpcResponseToJsonData(const RpcResponse& response); private: static obs_data_t* successResponse(const char* messageId, obs_data_t* fields = nullptr); static obs_data_t* errorResponse(const char* messageId, const char* errorMessage, obs_data_t* additionalFields = nullptr); static obs_data_t* buildResponse(const char* messageId, const char*, obs_data_t* fields = nullptr); static std::string jsonDataToString(OBSDataAutoRelease data); }; obs-websocket-4.9.0/src/rpc/000077500000000000000000000000001401107467600156545ustar00rootroot00000000000000obs-websocket-4.9.0/src/rpc/RpcEvent.cpp000066400000000000000000000021401401107467600201030ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2020 Stéphane Lepin 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 */ #include "RpcEvent.h" RpcEvent::RpcEvent( const QString& updateType, std::optional streamTime, std::optional recordingTime, obs_data_t* additionalFields ) : _updateType(updateType), _streamTime(streamTime), _recordingTime(recordingTime), _additionalFields(nullptr) { if (additionalFields) { _additionalFields = obs_data_create(); obs_data_apply(_additionalFields, additionalFields); } }obs-websocket-4.9.0/src/rpc/RpcEvent.h000066400000000000000000000027031401107467600175550ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2020 Stéphane Lepin 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 */ #pragma once #include #include #include #include "../obs-websocket.h" class RpcEvent { public: explicit RpcEvent( const QString& updateType, std::optional streamTime, std::optional recordingTime, obs_data_t* additionalFields = nullptr ); const QString& updateType() const { return _updateType; } const std::optional streamTime() const { return _streamTime; } const std::optional recordingTime() const { return _recordingTime; } const OBSData additionalFields() const { return OBSData(_additionalFields); } private: QString _updateType; std::optional _streamTime; std::optional _recordingTime; OBSDataAutoRelease _additionalFields; }; obs-websocket-4.9.0/src/rpc/RpcRequest.cpp000066400000000000000000000055571401107467600204710ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2019 Stéphane Lepin 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 */ #include "RpcRequest.h" #include "RpcResponse.h" RpcRequest::RpcRequest(const QString& messageId, const QString& methodName, obs_data_t* params) : _messageId(messageId), _methodName(methodName), _parameters(nullptr) { if (params) { _parameters = obs_data_create(); obs_data_apply(_parameters, params); } } const RpcResponse RpcRequest::success(obs_data_t* additionalFields) const { return RpcResponse::ok(*this, additionalFields); } const RpcResponse RpcRequest::failed(const QString& errorMessage, obs_data_t* additionalFields) const { return RpcResponse::fail(*this, errorMessage, additionalFields); } const bool RpcRequest::hasField(QString name, obs_data_type expectedFieldType, obs_data_number_type expectedNumberType) const { if (!_parameters || name.isEmpty() || name.isNull()) { return false; } OBSDataItemAutoRelease dataItem = obs_data_item_byname(_parameters, name.toUtf8()); if (!dataItem) { return false; } if (expectedFieldType != OBS_DATA_NULL) { obs_data_type fieldType = obs_data_item_gettype(dataItem); if (fieldType != expectedFieldType) { return false; } if (fieldType == OBS_DATA_NUMBER && expectedNumberType != OBS_DATA_NUM_INVALID) { obs_data_number_type numberType = obs_data_item_numtype(dataItem); if (numberType != expectedNumberType) { return false; } } } return true; } const bool RpcRequest::hasBool(QString fieldName) const { return this->hasField(fieldName, OBS_DATA_BOOLEAN); } const bool RpcRequest::hasString(QString fieldName) const { return this->hasField(fieldName, OBS_DATA_STRING); } const bool RpcRequest::hasNumber(QString fieldName, obs_data_number_type expectedNumberType) const { return this->hasField(fieldName, OBS_DATA_NUMBER, expectedNumberType); } const bool RpcRequest::hasInteger(QString fieldName) const { return this->hasNumber(fieldName, OBS_DATA_NUM_INT); } const bool RpcRequest::hasDouble(QString fieldName) const { return this->hasNumber(fieldName, OBS_DATA_NUM_DOUBLE); } const bool RpcRequest::hasArray(QString fieldName) const { return this->hasField(fieldName, OBS_DATA_ARRAY); } const bool RpcRequest::hasObject(QString fieldName) const { return this->hasField(fieldName, OBS_DATA_OBJECT); } obs-websocket-4.9.0/src/rpc/RpcRequest.h000066400000000000000000000037221401107467600201260ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2019 Stéphane Lepin 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 */ #pragma once #include #include #include "../obs-websocket.h" // forward declarations class RpcResponse; class RpcRequest { public: explicit RpcRequest(const QString& messageId, const QString& methodName, obs_data_t* params); const QString& messageId() const { return _messageId; } const QString& methodName() const { return _methodName; } const OBSData parameters() const { return OBSData(_parameters); } const RpcResponse success(obs_data_t* additionalFields = nullptr) const; const RpcResponse failed(const QString& errorMessage, obs_data_t* additionalFields = nullptr) const; const bool hasField(QString fieldName, obs_data_type expectedFieldType = OBS_DATA_NULL, obs_data_number_type expectedNumberType = OBS_DATA_NUM_INVALID) const; const bool hasBool(QString fieldName) const; const bool hasString(QString fieldName) const; const bool hasNumber(QString fieldName, obs_data_number_type expectedNumberType = OBS_DATA_NUM_INVALID) const; const bool hasInteger(QString fieldName) const; const bool hasDouble(QString fieldName) const; const bool hasArray(QString fieldName) const; const bool hasObject(QString fieldName) const; private: const QString _messageId; const QString _methodName; OBSDataAutoRelease _parameters; }; obs-websocket-4.9.0/src/rpc/RpcResponse.cpp000066400000000000000000000030761401107467600206310ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2019 Stéphane Lepin 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 */ #include "RpcResponse.h" #include "RpcRequest.h" RpcResponse::RpcResponse( Status status, const QString& messageId, const QString& methodName, obs_data_t* additionalFields ) : _status(status), _messageId(messageId), _methodName(methodName), _additionalFields(nullptr) { if (additionalFields) { _additionalFields = obs_data_create(); obs_data_apply(_additionalFields, additionalFields); } } const RpcResponse RpcResponse::ok(const RpcRequest& request, obs_data_t* additionalFields) { RpcResponse response(Status::Ok, request.messageId(), request.methodName(), additionalFields); return response; } const RpcResponse RpcResponse::fail(const RpcRequest& request, const QString& errorMessage, obs_data_t* additionalFields) { RpcResponse response(Status::Error, request.messageId(), request.methodName(), additionalFields); response._errorMessage = errorMessage; return response; } obs-websocket-4.9.0/src/rpc/RpcResponse.h000066400000000000000000000033771401107467600203020ustar00rootroot00000000000000/* obs-websocket Copyright (C) 2016-2019 Stéphane Lepin 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 */ #pragma once #include #include #include "../obs-websocket.h" class RpcRequest; class RpcResponse { public: enum Status { Unknown, Ok, Error }; static RpcResponse ofRequest(const RpcRequest& request); static const RpcResponse ok(const RpcRequest& request, obs_data_t* additionalFields = nullptr); static const RpcResponse fail( const RpcRequest& request, const QString& errorMessage, obs_data_t* additionalFields = nullptr ); const Status status() const { return _status; } const QString& messageId() const { return _messageId; } const QString& methodName() const { return _methodName; } const QString& errorMessage() const { return _errorMessage; } const OBSData additionalFields() const { return OBSData(_additionalFields); } private: explicit RpcResponse( Status status, const QString& messageId, const QString& methodName, obs_data_t* additionalFields = nullptr ); const Status _status; const QString _messageId; const QString _methodName; QString _errorMessage; OBSDataAutoRelease _additionalFields; };