From 56f7519fb49c291e2c687b5d2c3b41655843ee25 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 21:33:15 +0900 Subject: [PATCH 01/91] Create index.js --- src/components/external-video-player/index.js | 223 ++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 src/components/external-video-player/index.js diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js new file mode 100644 index 00000000..5bb6219c --- /dev/null +++ b/src/components/external-video-player/index.js @@ -0,0 +1,223 @@ +import React, { Component } from 'react'; +import ReactPlayer from 'react-player'; +import cx from 'classnames'; +import { defineMessages } from 'react-intl'; +import logger from 'utils/logger'; +import { ID } from 'utils/constants'; +import { getCurrentDataIndex } from 'utils/data'; + +import './styles.css'; + + +const intlMessages = defineMessages({ + autoPlayWarning: { + id: 'player.externalVideo.autoPlayWarning', + description: 'Shown when user needs to interact with player to make it work', + }, + +}); + + +const SYNC_INTERVAL_SECOND = 5; +const AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS = 5; +const ORCHESTRATOR_INTERVAL_MILLISECOND = 500; + +class ExternalVideoPlayer extends Component { + + constructor(props) { + super(props); + + this.player = null; + + this.autoPlayTimeout = null; + + this.hasPlayedBefore = false; + this.playerIsReady = false; + + this.time = 0; + this.buffering= false; + this.lastTime = 0; + this.playerUpdateTime = -1; + this.primaryPlayerPlaying = false; + this.lastEventPlaybackRate = 1; + + this.state = { + muted: false, + playing: false, + autoPlayBlocked: false, + errorPlaying: false, + playbackRate: 1, + volume: 1, + }; + + this.opts = { + // default option for all players, can be overwritten + playerOptions: { + autoplay: false, + playsinline: true, + controls: false, + }, + file: { + attributes: { + controls: false, + autoPlay: false, + playsInline: true, + }, + }, + youtube: { + playerVars: { + autoplay: 0, + modestbranding: 1, + autohide: 1, + rel: 0, + ecver: 2, + controls: 0, + enablejsapi: 0, + showinfo: 0 + }, + }, + + preload: true, + }; + + + this.getCurrentTime = this.getCurrentTime.bind(this); + this.setPlaybackRate = this.setPlaybackRate.bind(this); + this.seekTo = this.seekTo.bind(this); + + this.handleFirstPlay = this.handleFirstPlay.bind(this); + this.handleOnReady = this.handleOnReady.bind(this); + this.handleOnPlay = this.handleOnPlay.bind(this); + this.handleOnPause = this.handleOnPause.bind(this); + this.handleVolumeChange = this.handleVolumeChange.bind(this); + this.handleOnBuffer = this.handleOnBuffer.bind(this); + this.handleOnBufferEnd = this.handleOnBufferEnd.bind(this); + + this.orchestrator = this.orchestrator.bind(this); + this.autoPlayBlockDetected = this.autoPlayBlockDetected.bind(this); + + } + + autoPlayBlockDetected() { + this.setState({ autoPlayBlocked: true }); + } + + handleFirstPlay() { + const { hasPlayedBefore } = this; + + if (!hasPlayedBefore) { + this.hasPlayedBefore = true; + + this.setState({ autoPlayBlocked: false }); + + if (this.autoPlayTimeout) { + clearTimeout(this.autoPlayTimeout); + } + + } + } + + getCurrentTime() { + if (this.player && this.player.getCurrentTime) { + return Math.round(this.player.getCurrentTime()); + } + } + + + setPlaybackRate() { + + const { primaryPlaybackRate } = this.props; + + // Rate depends on primary rate player + const rate = primaryPlaybackRate * this.lastEventPlaybackRate; + + const currentRate = this.state.playbackRate; + + logger.debug(`external_video: setPlaybackRate current=${currentRate} primary=${primaryPlaybackRate} lastEventPlaybackRate=${this.lastEventPlaybackRate} rate=${rate}`); + + if (currentRate === rate) { + return; + } + + this.setState({ playbackRate: rate }); + + } + + handleOnReady() { + const { hasPlayedBefore, playerIsReady } = this; + + if (hasPlayedBefore || playerIsReady) { + return; + } + + this.playerIsReady = true; + this.handleFirstPlay(); + + const { onPlayerReady } = this.props; + + if (onPlayerReady) onPlayerReady(ID.EXTERNAL_VIDEOS, this); + + + } + + handleOnPlay() { + const { playing } = this.state; + + if (!playing && this.primaryPlayerPlaying) { + this.setState({ playing: true }); + this.handleFirstPlay(); + } + } + + handleOnPause() { + const { playing } = this.state; + + if (playing) { + this.setState({ playing: false }); + this.handleFirstPlay(); + } + } + + handleOnBuffer() { + this.buffering = true; + } + + handleOnBufferEnd() { + this.buffering = false; + } + + handleVolumeChange = (value, isMuted) => { + this.setState({ volume: parseFloat(value)}); + this.setState({ muted: isMuted}); + } + + + seekTo(time) { + const { player } = this; + + if (!player) { + //return logger.error('No player on seek'); + return; + } + + // Seek if viewer has drifted too far away from presenter + if (Math.abs(this.getCurrentTime() - time) > SYNC_INTERVAL_SECOND * 0.75) { + player.seekTo(time, true); + } + } + + componentDidMount () { + this.timer = setInterval(() => this.orchestrator(), ORCHESTRATOR_INTERVAL_MILLISECOND); + } + + componentWillUnmount () { + clearInterval(this.timer); + } + + orchestrator () { + const { events, active, getCurrentPlayerTime } = this.props; + const { playing, playbackRate } = this.state; + + this.time = getCurrentPlayerTime(); + + let primaryPlayerPlaying = true; From 9e5764528d0d02bc0862f38eb5110a29f3ea8c62 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 21:35:19 +0900 Subject: [PATCH 02/91] Update index.js --- src/components/external-video-player/index.js | 101 +++++++++++++++++- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 5bb6219c..2a71efd8 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -215,9 +215,104 @@ class ExternalVideoPlayer extends Component { } orchestrator () { - const { events, active, getCurrentPlayerTime } = this.props; + const { events, active, primaryPlaybackRate } = this.props; const { playing, playbackRate } = this.state; - this.time = getCurrentPlayerTime(); - let primaryPlayerPlaying = true; + + if (this.time === this.lastTime) { + primaryPlayerPlaying = false; + } + + this.lastTime = this.time; + this.primaryPlayerPlaying = primaryPlayerPlaying; + + if (active && !this.hasPlayedBefore && !this.autoPlayTimeout) { + this.autoPlayTimeout = setTimeout(this.autoPlayBlockDetected, AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS * 1000); + } + + const index = getCurrentDataIndex(events, this.time); + + logger.debug(`external_video: player time=${this.time} active=${active} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}`); + + + if (!primaryPlayerPlaying || !active) { + this.handleOnPause(); + this.playerUpdateTime = -1; + return + } + + if (index && events && events[index] && events[index].type) + { + const {type, time, rate, playing} = events[index]; + + logger.debug(`External Video Event: type=${type} time=${time} rate=${rate} playing=${playing}`); + + switch (type) { + case "stop": + this.handleOnPause(); + break; + case "play": + this.handleOnPlay(); + break; + case "playerUpdate": + if (this.playerUpdateTime !== time) { + this.lastEventPlaybackRate=rate; + this.seekTo(time); + playing ? this.handleOnPlay() : this.handleOnPause() + this.playerUpdateTime=time; + } + break; + default: + ; + } + } + + this.setPlaybackRate(); + } + + + render() { + + const { videoUrl, active, intl } = this.props; + const { playing, playbackRate, muted, autoPlayBlocked, volume } = this.state; + + return ( + +
+ {intl.formatMessage(intlMessages.autoPlayWarning)} +
+ ) + : '' + } + +?Z3AiyvZQ>doYp(EyaLt6)M~B*3&w5?vU`FYqkr1jJi;A4mpE~s}=R^ z((kfHhs$g)zf8R|r@G6%brd{~PvG;SxFpQ87_6O2c`tvYiE0wTa)dYXD~u(55>-xa zHs89$q=*9j8>>vb@8F{c5s!4~%T>|mod2*|j3uYZ#ZHYG>%5YMqvq%bu_)p^$#!Qe zpe? &v=3w^Zg4buCA_YfYOD`~!9vQTzHh|oF-$0s-DKNT}z0$hK zq+QDz!mMP^L*5az!ko^^!ou1b#N|KGY>{iDtzzGy5DocOOr#u+U$bDHr#`8!Rsp53 za4CgSb6GI9tIE2<#$q6AUi1BNz6M6s;T|jBaUH&geyO&A?YHnBGS#98x&@_lY2#=q z;)Pt==Bh^ZA}U(tu{`uYoCE2bNisZ*xy4hb+8Qlk5r?9x_f@kU%k#%XLp3uJ5fYLS zxg%AJy_w-soB=r;L%Z@Nwl8DICASX0rX8f9nKuuSSS{}098TaJ?25y(iP9HPlsFqB z0s2 ^%N RnWp3&b{b?YL1`-~Q^XXyf>h-Tx{k=8l<76MvozbUwT#oOuiRR-Lp znT-p3??2AI #sVLTpBx;(;(P?V&8Mmr El9P&-|Kp_?7{bGi !1cA3_ZcksPacnI`Y)r8hwa!hX z)1`xHPiY-1uPj%xtH|MRR^>(Ew>g4ms8h?UmNwrvh0oSN9hg*9O9yMfdQYTzQh$c$ zn^k8QhRJg>q{vhh15A(P)N6q&r`Y$TdGDJVw@Ydi2CtKq&z_X;S6Ak#x@vR~+cZ^f z9+oDEbzkn}lCzVNWc24>eB(nnwLP94 7 )*_X6~He#=%ce(_xD$+YPz$Xi*#-(${G35iJ8I@d{FQct!cr0D4*Mv9Mkf@7|%t= z6v11ZeMI0ShNKDwC2*;OX6cc$Ds;*93EIxa)DoD>WstH%nd8pt?idk))(9dzKOynG zxR+_T=`O@M!ENgo1w>seoyc`rC@}9Lt!-~p{ hy zdi#9DUi *%J&fnH$S>jq3I z4-wX{^&3$Reu7!AT1*#$8FYl9&)*{%df #+K1hDs>gGaYInnxY(NK-yVmPX>BM1QR`ROntRA}!$idq zy<=}*jqiO8Fx~$ vXptg0Iv0x)}1Z^RuY JvhdfqiMT8wg!#HO-XKWr?71Vkq(@=t5I@r8a=Tw zvzT4hmg&MgV&=5mdb%cFyOPQN(N(&QH0!RrDTuV=TF+V)T+2aUIhQuMdpuud+7X7_ zJ;ZizogvEI44+*)bA}{~y_^B!zQR*7Iyn$nB>FWrdGs=Q^k}SDq=x#szr2u;zAsU6 z5aDun*y1C^N5uV>)I;MhhPBh%F@=|*x{2)Kp>;=3?1|PhCoSS78_q}kLR3p$XiJ9a zgl 8tQP5uF+QM}LNcCssRCNJ+RZn$*zCuCTo)Qe2!N36~=vSwTPV!*l;QJB#j{ zerV$KduMiT>a~O!&E0W92)!^whO&O_hknBAuBiK!QP(r x;@1IgR7 %sr-+q>#CjQtZLqd zy^hQRm6kq8kw0k`mP zDTtqIj?U9PYL${X0lz5LjHm{+DkXm!=Y*~2yRDKjrce@M`vK^~w2_itk9IpfdkkC^ zy7bN*_}9 !f&&qeGc{Jy iN#}9zOp$rDz*-cCk(iaW3$0xq(DL&pySZAs1ODGjP!<9io1r!XiXfYLr!}5o zQ~AP4T5h@Jh}?5yu0Y-jaC)hPNXvw9Q51x5ugELZxubQr>~a5L`k#qw{)vfjE~)RP zf{?PEd;Lk#&8@M7O_Q)DX^AU)#4$M5=YCI(b^KG}c!WPt0AHfT;hzw*OHRO(3=OJX z#>WnB9YJzuh^d2Sgiuk4cmB#ZI_PnUs_ra$ZQZu}M;7&S;nz8feDkjaCa9`!9FP*e z;QaR zZim`~-3x71IL1?gWc*J+HT)0?W7t2x*y$QEK8m3gm!X>iZ;u$XksHI-&BkoqI{$wB z$Q{wIXFS^IoiW=s%9kH>PjG#=ynDSy1+2D|$z%BIEe|om$yi*C2A3f2=Oo9WbQXH* zd?rKs8*K+Uy3PcYH;MMLvdU02)TAU-#n}D&Vik>|rZO{Rso}~6SJLCam=BCa@=rKj z^3QLn{98ujx6Nn&=#`BPE>5WRm6PAQE90`U4xO&e8UZf*+J!JFaoYQcR_;5H9I)F( z7;1@u7Pr)fr~ *~GMSrV=Qn zoc!mj8cwC$Fmr%x`^N>492}#6BY}|yVf}yY(RbAFP345X=`2di=~FV|`iz@w53TBR zMiDzapU#h<7)6>rcg_dwVH$wP)kB6Xoc!7~h;R<>4zZR&ledA2SAw!AnxlK8nmA!| zxj7is%a>=p@^;4h^M-3Z^)7|7BS(<+fUyN~h6J&&m#Sj@h_GLd0tTa3C9=Bf=WC!T zY;oOf!MOh9JzWHtigA$5c*@RpqjN4k;stD9@Hw8|?g{pP+>kyCWuL6rY_{qkW)Nuh z7#&>P``OK}v{<@nBc=hW_miLOiPw5Xn|ihhOpR_xwJ}=v1k@{UXur1+`68E=FZ>01 z_%H(XBsGf`#%m(GQmT7$`uXriu9;rX7GSzT_P4Ti{%)f>XC7e75y)-yemM%GRaU|K zwp)}!nOd}k@rhRiB<+_Q7%4|3XE4aC$QM)uRxazV$qi_yQP9FGv5p6k^gaCGFs=lk z<`F(L{=lRq+zmEJP8XlwIvCR3l;?5v5iaiDgR*P+x9sWa=wFy$mZDjST3N*|W?)6> z&~YMQ4qm}4Bal^ _dmE62
O!-*39^xGX^)o+d*Yf=ZK;WG(B4vzXEfIcx#*?*YFgK@vSjtG{aA zcLyKh^fM`3oxE0}y~6q9c|9vrFLo*LF@x%&%}P^!BzCUBjbJ=Rd4!$U+jW%S!L8HV zO~^kVykJA^69gk&d|$tSE$D#zQIgvNWh)|(E-%YzEtAX;_-Wx*)p%F_lb+XEpqxq` zy<9_L9VHHWzB&2!c#2d%iO5Y(!F^;0<~H ED`oh*NX|57mB=> >hY*-Uiptb6RdE_s&LKqg8eoDK zvC!nxk_9|0cELl8hWYhjom4!v;ID*_lks~vglXhA;!8(I=AohKsIEXnnqw;OL*k^2 z1WpBHG;~(;^fJx+&S+g~OyFIkNxpqRMLR^YI!}bt!;G14vS8Z1Jp(TLiz|DqkR{$3 z?71l4!x^0#J=0~D Ocx860?j4ua|in&`hn z)ipZ!bUsAtoXK1dY6Py_OTW+-o#9rz |jxN>NJ?Z-Iej9KtrL{{Eg@Nh7flT zu$=3ue{P3a1*Za&kW8~SfKqQIZ_NjHd~k3`HWmiS9i=%b#5LA&LvSS4`EVuTlkvb} zw@WB?%X;P?7Z~~HUs1WeiUET!;z0I0xSM*8PuydA{i91r-g5Q~*s*_XVrNOBj5|Um zvVs1qhvz@ZhB`NmXHA^q&Ha#Gi)plO6RM<^#WTbyaSVW*`HHHJgM 5NKtVvI2qtii$jR&^i!Eq=`O@4Gi&>|E^Veqq9`&IK>|Owz z^vo=+Cx1x55ar*w)6ENg2XVv$&NEyTN!Eo*IbY=Y>$hRXf<+fj(+lA+bQnOkD}QjW zb`o;{Px}`gAxM8Ek;g6u=t+FK(>r-T{f$v)g{ZC;YUssIk$p#?02D#am!Hi1%Zkvl z+S<2|l#f=nGxo8*0$U}G4SvLOCRYi)Fyc!>>*{qm$i%+yW_rnob3~)tPrSRI;9haN zz9l^-0Ow =DW z!g5D{RadydLv<>om{|cnAlZPMKOx9}KXk{1^faXHq>n0s?CbIh`})#HfVzzY*X4+e z&4|WqT}CEIh8+KeLDEK$t4{0_5HTnz!hA!ShNbk_2n}*QcO%aI4VY{r<@A0JO=UBC z5AEw4^X2SwQXeVV2FV=c8tIEvbJe%KJsAW}<#*{X{P;fs`vV00`NkX2)l}XI6X>HG z0P<2x9DF#D488a{@l|w9rC}uU?jgkZQa6a)Jur-eADj^9_)C&_eaVPu3q?Yoz87bA zJpB^rNrKrT;ciKbgG;zO=^W2^lfGCmC>fbL2LMM({zkz+vjdW!7 C}=D7FnfjyLchq;|(ULwh_#;7~Gd=mqfTKS&jk0FQ= 2onJRT8s#`*Ohq%Mf_3rKx64nCAnqQw`$BXmurVI*Is4G-QepqdjTpX!pXtv^)N z`CqFpxLWm9F7>Xftcgp(tN&FBy01!s$eMASNZ}Bf*YIkHP5&toaW-{dWm9*2FbDBR zcZh=t70-8GGoUCwn3AOH2gSjJ tHR$xNj*nNeU_5Cf|A<(pQQRIsXtIsPf$`v za>)bUF#}KX!BHdnMjZAzMvdwndC=u@jJp0-$*rl9RQkV=rAnTb%$IDGcqCgSCncXq zE;H9N)0l;fifLrFGY6To3HKz_QkQ$!-KH+Md}RECDz331(4-KOW#Niox!fv#8N?CY zcvfpRn~;_xkI~b^RXz(hlFa@T;n8^ZC+tYFMf|#a3)v!mUA_e_`t>>7#jnq|C`SI* z|CwhIlp4h^UW|&7Yq#a?+O=)lu3dTC)~?OVTPq()bYdO)J^Ix*q1uxy8t4S*$|mf%+KLgQb;gEu<#CH%%~zJwq7oA2P4iT7m?Gt%~!iQmDG6jw&d zApOsk5h#a}T>ydbDsf$;maH0DA6bQ0;aXZQu8WK$mALY1ED-xvmforClK=0n{z!Za zxtSylIrSr}eERx&udlw|Fuc65P{&FAeB)TKP!PNP!1)lNwZi>iyK}-k&y m1F#rH~oMT{QU|`?@Vlf6e1||kZAdCTG7N7_d5HmtB z01qAjs{jB1000310002h?M1i%0002ghQx3H0002qX#sn9oJEc?3c^4TMIW*@K}@0% zas?MG67V9nS_Fi2f}NH&R>nflmg|W5GZFm58)pCfT{e_3nL&U=EJPlHfPytCxmPCU zl Date: Fri, 23 Jun 2023 00:27:19 +0900 Subject: [PATCH 40/91] Add files via upload --- src/styles/assets/icons.woff | Bin 0 -> 21276 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/styles/assets/icons.woff diff --git a/src/styles/assets/icons.woff b/src/styles/assets/icons.woff new file mode 100644 index 0000000000000000000000000000000000000000..b491aaa7b4d334abb0a6243800a2e5337fc33d9d GIT binary patch literal 21276 zcmZTvbBrf2w;kKu*|BZg){br4wr$(yZ)}@8Hg;^AZ@%xn|6Y^Rn{#s8rcKl2-nMS? zVq)^j%JM)!N;p8UKtIK{3kdi>_Wu`QF);!lAkbAHAUPu-ATyO7`omT+F=bgGAVvKj zZ=C-iIY+5eTtrmtN8|b7L_Z({A_q#4SE6SE0s=Pw;Zi@)S8dT!G_o PKV!0o$*L$uILC@kbN*;RHV*fszKPFt>4b{~5#khv)v_fRG&) z-p0WFr$1o*9}e+fSp5OAHLx-H(VBk1{xgPaN)sxAy`7Wu&pd{Hd{BQt@ZAc;?4E6; zZ>+BmwD<607q|av)5B!n$l%|X(n1VQ@D}iiJpS$Xja^S;_5|d}`72$Z5Xh<=9{i{A z|FwI1vFk$`B)a{+{+38RgcCqU>q7uam;!$X0{R04Wc9y(24B-00Rbri0TyHnSP&4Q zzEIOInf>Vrs1y@@6H`E-(I9Y8NbyIR1AqJSZ 8A%&>jnc%(|mo#W1(<8QcKm^%Gm%d}U#%u_de9>K6(UU +*@IUOsRzVL`U&hfI_p48(4`FHYO`&vQu8XRqO};Z2Lkfi^-FX zVzQ6fgzie@q{z)j+HLkE%gm NZC@FHOGUAq&k5DqG8{c z3Xuw?Ln`G^`X$T7N~Jo%BC=`CCd-AZw4JY)D&aD^EzgIu1-rg{!a=d<-^{iH#gq%C zQ<= *4rjg0$8_2qj+Y}f+|PJCo-jv-_z?VY zbyqvQy1)-2_z?fygQh@1-&((-vFd? Ngu{Pm;TG0kcAiRRd&XO{CgR9ZqxYL|n~#PljN!-F(3 zgLHTp{1tjLAB^rFr-ON*`L4)5!CW%M7ny~O;~wCEWNkNCODauf;N?xWVNtB{N@gA9 zGr2b@kKth^I9TgC8Sd>d2fBe9Jq|%yeex`k3{Lu2g1y=RFZ4c2DG5(51FwPUKz6Vi z$~>wg>K$q%3Kgmh^b?q$knlihFfa5w3IZvPBuC1P+`w=!J*uxnxC76#)s^cNoh{t6 zmo3vX!!sOvn%01cy;Zw+Z94UFB?qzDzGWMPEpcm>R}%NM&Th{?-`!d`p&(yY3YWi^ zL1GBMdCkxAk0GY9li4VI{ygP32PFqB23ZGLLcBt-!bQTZ^PR*9l0)X;Kb+>%Lw@oy z5<0l=oeWV%%#zH=YA3X@0caG`%w?KL*+|4t+{k>B-6?NJy8>CkNr$mQLOj|tu!h^g zU(aD9(LzMQe++s_yGCT-L*T^)N=adPF#~T=dAWc}VqVEuFs8 qi9Tfbiypz_)zHi4zhM_&7=J_@?kG9 zuqy!NqNR<3o!D=>PdI_7@nuJTII>~yMb?$Bv$V>wF;av5o+)2Vz*Mk+i2sYPj=)KW zP}}i-#h5brJQ5ZLQi_5(3Jr#Uh`#Pyu7ZLtRAyo?JMpn9 |nZ(aW5)k-lQVjtNw9|lQ^5?vldQ$O#G7|msb-utkm*_ln!}kk;M?mzimHWz0 z-=p|4aH{=fM-NFI2fI#6l2*04PP{_$<8^0YVBq6KqPG|&Lq$FXwVpyX6-8iJbs% txAw=#zkw%_ElblBRn0i{S+MmP{iV1~-03qWbxdUSiv{Y&*n8-+Jrl^$4=t6IO zlyEk^j2a55xTv`1yt+StH?$D&_SfM^qQ4<8I|A6C*=CMCVhEk|G!$S@0T%+gm2Hv6 zC1$2*Oz2Z%N@(bqrUy0h^orHs H3x&1nGvBf(T)pVzf~)|7G@*G~3r~lG!q^ zjMzv@9JM%Zv|Vl@*pj!xMlV@y;@Q$abG628%y#$V?jksb_4(r-t+x|zDqQ_?%jWLS zIpAt*ZBx=F|JA~*jp>%^mBl^k;&g3)+>UppbH#h*a|L=O%;1UH9@iGvE|0R_#&7R; zz<0>E|25<@ u=B=JQ?D^#~N&+PuGpU6Y&A=z-Q=JruZbJ$8b8tVG(IZ)RM z_fEpfvB~toZIF0#c=^25^W7@#x8XBN@3uUCl}eAN^3lm3m&^IKnqGI6!QeX9efn(s z`f0O2wU2#emBC;(P= s+juFSH;^%x|M!ISxBzGI}v z^L5BFh}xL9`h0LNkC&y(FZ{KbUnVxI&8_79R%XWMG4nbwkNI17^HFi&qVC9frg_$B z_WQ_W2}M|QF!EWIv{v)AE_qd$@}b0SXk#YaEocA3jyw_aly)F;$|vk_PI8|8`I4LF zU3>n*%Dj9fnEC)w8qecji1>kzQZZtl->v*j>F~np-*VWL@nJKGK!3(HVOQ&o(36b= zo9zoWuISa^7`Db? n9b4BP{zrOZu? z)?urAeflS^O}b>JDlP35Tw6pphnTIcOYx13jbT)si0;OsI9N3l5)bu?ro)0BGz;A- z$Fyp8!G`W;mrkBcqODr%>7 )9Qb}#hPtF#(b#}cjf@qwNcVuG7BvN}R z!bwR!Ofv8TheSj_@Q6X!!Gmaq>#;j#9r4&~&c=_iO^?a6*`x?!vHB+FPmBAv1+dg* z7g`%SY3Wx>c}Ps(EVyej?=*A1q*X1VFIfh6XI3)q4yN?lK?A;O=wz$K-!aiMsg=Si z|Na>1bi=)m-SA}3%$}()ZY_C~YmwQOf?Suo8uqSSI<`ANMIci?sl7kyF0#`dVcfw4 zD0ju&gnTsMBuSBpJrabkf)ka!Z;QHnJ>(g~=b>DnpzCNCOJ7_#<`Of{l0DIViu__t zKRAZqP>YhNV-h!R`di?lF7P(gii@Iby&TBF;lPP(QGhr#{}IG>cWGj%wzQX4Xf; z4Lcp*i4}<=8ZxhB=3unW+1(2B=4z%bC`dyj2>v2=2qeLQ2|3&ovDnF9tqCkg;>MIP z+Be+yaM* MHyL7dAj34$f zCq8_CND!nwi)4^7K&BX{JxQ*3-vT9N&;v$LS!!g$0%DCSzSzzm3|jK*z?Lf=!Aqa& zkdpr}&2K_+tb(6JY&3ESWJ_H4I7YWyIxYYj!|3jpr@v6-+yu&l9B8q-tZx 36QG$e$NN7>n7=-4KR}76B(7TRq-RS;}0X~BIrx?9d!F{KsD&jaH zad=4 me~n zINi8$vmim}Vt1Z!;{>?veFx8aeB>hMqEb29D7U9(Tc$ftGpBHY(tbT;s75*>u 8llGAA#D9AdrcRN8@JCOEQD$ zH!oa^@9`jy9z}R=?=Ey?JTk6Fiv{&p58v!Ec*aQJV0lrpF}0-M28=6XskRy>&o2 7@j*V+{-qtA;N8#3+hIqI6#D+|WlGVU?GUht8bmu)m( zv);jFpjik#m{e325N+Z>v5zx&WGE|wU$ZW0ocpWwXr^r(#>UO&6PiB|Xo{#t3NzqC z2hbGl7q^6y6%Ux(?1&UxLppdqKuYc)@($3?;fqNs74FMB6d*XxGAg)5Z#wY+o^9`1 z{3R)UQoAIga9DQ4@`{*coAhTYn`d{;ZYKUl+EhK$pNw~hD~w$ujt>Lo^ZD` ;tU zEi4J><0B3b?r= xEE$7!Ps-;hd%|o 6^7SV-IxhS!N#mgBNtcU z(VH7n^`+o!#=hnjWEcyi#UE(>LSGbaG0`ofbcOq2enA*;^D=rhgO8K%K;Z9RGPB#V ztZ7A~9<(G@Md-6mJV|E20?zMA^ND!mzpN8NyQ*meP}87U_f1&Q k5NAzw+!X$?EjZ++2ALtBum}JL`B6G<@K_r4|foR_%tjNpLO3)g{4pKT`+z%@sDO zOd4ypwx~u$)hp`h)+(Q^?DGVlhBV>zcq@$`@o0xf$ylnWq}1YI?mDVD=n#(Z&zI59 z6TX}I+&!2?j|o9z-A>LL2Wxv*^oCplx(8m4%i_}JLK2T}H)pz@ER4sxjk!if;#r}x z;XS(^^Q##s9#5EX$2(nIQn}!2+m|mNym)+%5=JB)#;~3SjruMJ Oz`C5$+TqE-ifR|`GBTA1*@bbCHcP}yuRB1N!NsxWyfRw7gm#`K4Z zVR_K$lAwe>B+udMi?ECisqhZ=*_$}W19Xh7#m{cHUmxiDo0jT_+nRmepZ42O^)g>? z)1ev+hMB%*Q?ASQPdU1g1bViO=Z)a+sOR18#nJ%F!BzGt%_v1)c^qB}vBXg jwC#$5#lan@mG zoL&+xbYynsPaqGCRdXxAow$EZli@jk+_2*A;A4|z4?q&t;W~TZIBeKHX7FI7l`O+8 z%b77{D2JRcw*Nxk?aHTWvOK27#{4Jhu5oJ_GHl4sH2cTOK0_O$ZYv5It?sdHUGtgc zdf29IxI@y1;=1NhT<~%D8Au_5csQYJ<-8I!1x6i2U$0w*4i~!-qX;=5BLvj(*Cs?* z0Q6|#N>**BqWtEsQA7x?bit)|=RYBU85Di=z+;hW@Ko3j3xZrt$O_#fXZ0wTxb z=lJN{rLA}FVTY|ji7)Ph=~{bo%1bF)j@wHdLZK^i#MY!?cg$^*jo7(r%f5n~uUQ>F$%rP3_y+#vcyogPaUTh75e%vuOSv_lPs$z2Bg{ESCC{j4O-C|${ z&BsIhS+@oZdt5Pe$eOF!+R_D0L+Hm>EMKq%`L&FgoPd3IS*?66@r(_h1>K%GwQb=c zD(Ny5=r&@Ph{^FtmBE7P^vECy?)R337rtK=NyyDT?-$q=vqP(^Ubg1d@$>-khv4~K zy^7=e^c*+NdV2DKnq)1Ye 4=%76+|tdt&N{^(G&}OQWb- zL`Qc)uW`IZmbB7oD-c}yLsWKdNXwquRWO5EtOXS9ip}pYJ9st%V?A=jL4_;jC+sI` z3%;C=Ys#j-%fG7mM_44ZZ-elGO+PxPU)`mgXbMghr{MzzV(`)#?3&R7STEQ?7&M#5 zRe&v=4;$MV6&WaIWJK`Zu5;MIfvX1v$aE^9sr;OZimEcw#2LXyTXWz9M!9KheBZgW z7;MZE3X*L?AB`@V?KMEoM;NQ9@n7h~tk}0hYE-?G;e!T1Bp;(Ah3vBrn^)$^vU+Y8 z#@hb$69VjpB_qrpssom4&lwaM*>R6cX=30|2G`mY6$uE)zlk9j`WQn1TC@o~mgsly z0BZ4M0j!94>R(c2$k9h?4UmfXncBPL`KY;5UYx5_*;wpYlmKPW!fb)#K>*IKWAi4M z!OEKe-h;}pHM*#hCjH&7l{YJ%RfYXNgf5ll_F`X|=j`j-xLtRGp-hQN6JE&&R!upx z1(d!tV!l!orWeVwX*Cn({9_Bgy4zD>CizW6-}zUI;5k2sthkYyE-$>m+yY8Vysy8w!Os1y`R zpGfnHR36ci=@c*LEQUxEk*HuO+bS5#zen)i5~!>Gll4++`M}4B02=eWg
+iVoK%IpwR`6Y96Qi zBnhb?rr08Q?8_O%9Kl&2S;B;92>1Q^TJ_LV=R^0#I&mBqI >7)>lV(ils74qi}q76`Aq{LQgcZpj+kVNU3*%FRJsYMeT| z!^^`X`T3(Z9d|}2Gg+e@I&TxcNeX+!`-Rg%j!bce5;|J`Fv#hJ-$JKXgOZKDqESRM zdjyF&5huABUrzYaV)K6D@OOGK{nKj?FHBSq0LorOuy0tbxu4;XdbKm{j@kCh+ezvq zcf00l9mdQvgW}M+$`Vc7*-4I!5Gjh;AC=zPL*{TGDLdb=y_0xu)h`(d1#3{)?AE8= z`n55gv`v9=L@S@g-Rk3^ruLptTYGqUf8cDGsJOJb{yU K{~T -DZ(vJ%Hg=AhCr`&gEr}P8|-!nQvZk^mE2%MRG`qWl2|@d}c4vWvF*F>^{UDFnOl* z`+bC4gjNW+1vLhZx%~MBkN1o7apWNEn-kmf$}a`Vs7Yz5p9uE}Hpk);7NfXim^qJc zbDOegUtGINPNPzBA~|-R!8T@cVh8tkylUGy{no+lRo&XDq8rh;tqIBodfF=87x!B_ zf$deSO=`HEACa?diR@k~_^bDo7$IFx(^ES^v4>2yBNJnUi+DxgvT}YOfm}OBp isq0KEiRpd}pChXl{Dc4}jJo5e!uQR=cREh%;~Y2{v9pYSn3 z{pJGh1wx}bDIEI^8XsmL!CM~h04LypRPy3e56r>W&AKO;?gE?AIf}HA#MoemH{Ly# zl<(K4W_qXd>1O+Anu`lAHw26;&bn7TrMeb2g}PR8Q~jS3s$_8;0DegOWa@N|m$il3 z%I&_6j&$7Pu`)oLrnR!ZzNzWY_AwEnP)__k#{mxnXBzwgO-S~1JTnNVN=fz1_IrVx z`RA`DUmQ(6uD>0ThlcI@HJCunPV1iH&JKB(r1+pXJ%3YI-`yYJ3$CfH3v0X65q*{& zt-a2lmX*F{8QvP|qyo({n3jGwYbtQU775+9vz=OYapgl*3BT#Ip^EC{N%r=YHQP+E znVdJ=6qU;+3T1xvMQ~!GdZoqpLIWU&-${{O)Jv$s%86Y4E(9woyR=IzX&`0X{4RVW zsH$U!K+zovU&2}D(PEp*GnMbq8k(cdX$kXSe=Qi0tQ|3M13NHSTQXwTm~!i*R=b^c zn=alP=E3TDr*T@?o5JY<1>gV9h8Qzw$f^m4ai*|qP~ZPEW5Gfi*PjkoG>)M4Jp>81 zLdVYW-WK<)^&!UY*~5J{z?^#w?3@L(l>Te^_BU|#XxYZ5oCp|G@^f1@+_y)KuKZm( z+S^~~yT$Q1xt6?-1NCyH`_$U~y;BqN&?PtH-1T)pYfw5Djrb>S@t*Qp#T04 0wCtww$y*ciaYxma_HN zfI{T4(LJnsM6}{H(+X1;n%P=|3=BoPl9|#*feX*mrqNE%UQ=gfP8;wFtH6dp?>6h> zwA$=0UNE7uV=#f=KJ6C~z0zx1`q`Uf>j5>|!UcOLMuHA|{BHP?*%@;G38R5~L0u zKheTC@HQ$;$9ev?CvKB*1)J8iIb6=n2F;S{O?(QW4Fy8=7O;p|A{Ae=+YEH;&h?$J z@ky=g`p1zwfl+PC>QuQtmK|$Awg9OmVe-5eT(c(|J ^NErP^G zIS3>c3^*?`5Ro2Cg<&KelB*~~o4i*}^9;uP>my#-);L)l0I{!^nlB<)pyWcvWlt$p zAbTq^d97<&bNWq9q0T}d**m5YVyiP66B(C~!!ifx pLNe+ lgE=< z_XsID1LO`~p9Nc;bUqHTo7WF719a>U4HzP-R&MK-wD}h*sW4V5qGHQD(U+=7Vd!b> zsq8{X@Qm%KnmmGJAa;p5c;wMwo8iL0|ELCO%zD0FB!90;!eWhd`ZJL4e*Xa%-oW5J zFF=PlYX0b-@Z|Xm);Keei%37ZiR6h?k6oY4#kiAeiKa@Eg(w+yo{!xg7Yyj{lr;1)t^ueZ>1@ACzLPxQ% zm_7lsLNOJEmVfY(ZzE;3lSh*pt>q95odcepc6>|#&^z3-XT#RHWGl3PD%Z0fx>n$p z*(HY9hok5Hi^CGhf(QqZ$YcR!Z)eEvg?)R~9S48JtEm%a<4<0lT{r<-gbPUuLX@bX zU@qTFfIypq^V_|56Sb#QdEG $ExK$>MhK32l!Hbc?H}!pnvesqtFc zEZ6a2(s^F+8jprmH{)P|P~ f#@4^B652>QB$5@xZj8CfP(D~F zUNOxhp$Zx%q8c7PqDh;;Ow~1lb8wP2&5gg6+bens02L?$OxP`|8&$}x^SDCQ5{9EI zFsW% OoU262C&kRxfx8iJl4UJ z7}sg^@@a{&lbbx3UAE_vVJOi22?G0F2N1SG|NDE_#DlhJWXQ;W`E0L71+Z)=Y8B$Q z0zf|-n@tt%4Wk>PAVwec($-K_)#LnY6fHs(u>Pwe2L4f27X9%sS{bgmR9kjzW~L|8 z{+kz94b^w=Nmn-AKDje iG&J@V7*uuOCA>qWK~7EJ}&7{B( z%VyQOR0GYN Q&3IIg51&e<27 zf!ve*z!li1jZwJ5rI?YAAq-l`)Sf=lxWb_H)=Rf@t~{(e<<1OR7tikUZrrzls-QP} zjswKW{Ip98hQ*Bgr}{&BOd=0J3P@InA>O*qxiis8%un|5Aq)0qHu*iI6(}@ZzDZ*m z!phGG>Deph#Qt})k#5aG j#6KIWjnC7cJ!7qe!Axq9Cx3 lt`bKsxF8RUz3y$_MySvkn !ygG&<#O+Cvbw~pMVcXu^mO)Lds9h88k LNJxOHjP>7S{z( s zGI|9H?^MK&1Qa1XtAe_`(tOus)ZFc38VwCu-oB|_o?HMmabFC}5{~lw)0}Vw?sC3W z$;4O9mipbZ(QtEBb~~30L2+7{c0v;T27MKdoZ>sS-kG{#^F)!LN6#v}l7&TQR$R~2 z*5ypADH+lWRoKi&9=qoCbl8SPbxYdvB(kc7(|TuJyCp!zHDs~+{t9GuIGi+M8GZhE z7p^}7%W~bHtww<$o!x4nxy26LaJW;RVm=8ak1)P`L_=pulmXw~!8nA3#UKMFW8mus z0O7xYi~O78jtq9=i9yw8ZF68r1bmV~ggaj|VlDFY9jd Q@OoZ%%dDLPh|*~b6Aw$cT^&0O?bT&Lq-z-E_Zr{g zi@h4)sL>Mv=M%xMJUEk@0sZ@;Wx8#wa$vX^4Ak11nEc@`{7~Z@SeNEI-LMVRGP4st zZz!H#(7&Ctkuy}U{!40?VS?V1<1GWCXo7lxnF{e5Cy%mi8$%iy$z!A0M4Wt!2Xn4y zgm(*;dwKrPp_wAT!odSTf@D%|&&y>iCjZM5{q<}*lf_81 u|2jBS{%2#N@ z2DcM;TJHD>k#y(Su}%eG;Do 1=kriYzXw|18k|KvxsY^|AlvHpdbvu zqILn%4V5XWJ7yht`V#0>Y(uqU>l5_Qw+Hj 6%Ivte->1H7g$@51_0=y6KT{4Uh{VIIzR+i9)nhN5e# zwc29%OM#zP9tmof7n)_w;l(}-M;xvu6UovF-fpzBkLK?yh>so Nwb!%oY8PZ6?Z-~AV|Q`s!h3sidJD< 6S_R#@w2+#sDN5#gZ|qWMPu(@`7wH~&Kx+Kns`FGCb;H7;&vD7A7doWL z9(!Vt*Arf{xCiB*Ex6567$#)`;uD+X!LAXso5}H)9yr3M7?Lj}UiwGK58n)q=-8y8 zDuR8(Zj_p}@`Ms?OLA@mABUSpV(l0I7ZL6X8}im``Dg@E$-ogisPQr4#7RAG&)3ni zZzi!=zjQx~v;K=x89j_#e{xzKPSHzB$dT!Rs0G%>(nH$ZfHI;9zfYilCdSny2hSlw zZ~kI(qf+y?XZG8*12rk{H&(lxI;^(eYwYXX#1wlDV(zp3yq$szNflf;#;b#s=w4@(9K7jqRhrlf>2^~%M$Rxw53NA9J;0r97nb~ zmLT%=ad6$>UDGo(O!EhD!;tii{9eIF#qO*fa%g8J>pAZ7irmdM`A3{Mcx^GAx{w|< zawtgMRj0TnpD40_uRbr}p%sE^nGU7;LN2E*UVGGCo~^m4A~g;v0ca@OciVa-DMCyi zE-t^GK2P%TVxuo!Tf*SZ2(@SRJW+-t<_-r^X}toy7;x5U!EHhcSUFJVq!p|GR#OK1 zt^Ed{8CmNOqz$o2iHAmZZ*BFp)50dIbtQD#DKu> )9t~BZ;l^`0 zqOet;V(aen`2d5H3>+yV *_w+HmM?h~V%l#wO^LxT*?LDo9W(f8+ zF|j&2x;l~6ov?66>Xu7hupEf@9YpWO^bpz`WF_sL8fahxP@`$KR6l-imYL__g9oPS z3|6-^!j7CApe<1D3&At4bAW)C#8}jm%IG-Je5NR?qY ;X5s3b>(l{^F76v4ar>hmpmY8Q?4k zq#ZjWN!ORTK6D7M6%_;y9_5EVu%h(kZStBdmDSQk?2gqN?PS%0X+aA? CEmE zGg>Me6J2H(#mk?#=QMc3>984xN*CdlBYla!5;-g+D1DnsOG~TOg4T*Q1`` kXZDAE8jep=Ez66w3A2p9Vv4PZL8>NF6*eZ9@YL z8o_{rOlzEN`x;~t=R{9+&Y)%-L<$gNJ8aBh2d}aW@Q9e8%7VZNNAoUe9|<+&_0b1u zO{hsnI3^SdqJRhdiESnrq=~EwaV@D9! uQ!0a5y_b1;M48Q?H@|giZ$rTSucT`)3 e3tn65)?a8^T%ANf&F;;u#(vIViS|=!d zvhi;Y_%oBc8uH{JRx{JxP`EpugHE K$(lvFqfnF2_S55A;XrI;Riusj{!Zrdm7nUT;0sPMJ+&i=F2;WREj9 z9gjJco4XFnp_awrPFi9&kj8*Bs<%pI5g*9lRXoWverM016?*y4C>t*pVYm_Mt?jh_ zb9k}tXpN1PS*L?5H%aCXNMdXbS|sKBPq%aeu%bL)5&pf_RNT1>khO0 5=EBwabir_CwsL=h6u3G|_Z|mU zF5hhSCM?8%HQ_7^e1uU)Us<~KNh`w=PW|;$& AtArN@#}I#XcTiN_TDDblXir)$@40j^_1$I6r>%aG0`fRVY&uX;E|nij4IOYT|9 zh1UmWS$FiP7ShC SUwJuMW zS~kfnJEP=)#oX4I$V!=LWGF&x<^RRrKA7ux_!}JAT;yiOLxx5hUW25W??r1OJA6ky zD;YUC5m_)vJ2jjg6U!@}%R8BH(0DMEv0(OdKBIb}4$lTd2U*}@(aGS*kUd*~hyH&2 z%IA5@VL1TZC5bIgBBg$!LYUlrthte^OPOOnPy@+w3}ILvf10YV2hq>yw*(MnN}w{n zh9g=D|3s*RrG Tw =+Xrr5Mv|kHDezoQnIQIa?Gp}zPgps$X8ymplr1HR x&AE+@9Qlg`ru&vZ#Ad~uX>(y=;lstpeA8yLW((x-UOm9ZmRyRhcRe`>Qv-v> zBT?#-trL0=K{mfK7yh`|*0Ez_TXtPP+W8sbT#i>L6^Z0!O%8ac%=m=+7=fGpU(T5) zy3}WrLib!f7JeUY&GClbj^oj4txmJ`bI$#GDR|PBrTF eebQ6&_UL}|xv!b_fxG! x4tpY1cdr~? zx4h=@^U8SPjx%h0YHCW#$X#(af+^laQ}352_vAwEqvvn+lLxswv19TaHPldqy0%OP z-&w{suL~LS49ChZeV=27O^-K${)$l07yapgDrjh>ns_t2fu-R0d9~-`BbdL`Wpuex z-JVBa?WCEsGQYDuo3Dmeh{@o;JVjFE^+r1s%bwL#u=1nkEEY+5#9}2{l7S4*HV7kv zO4Z|%2`M$smT1ZOO2kQS!%keGgH#;>;=jYx)J9MU+HWg*#3W@>Ncga9ik05PB=^z( zKF*(v=0O3--!g~Di*qC~3G!GTEAK^i&Ul5EmV*0d&uVo)SM0Zzf)BLna%`ai3nn^U zPD~>LuS>xuN`(7My`)fTbzdLPST{Y?z%YDF`=x>LsrHi@s28^)e!q>eB!Ub(0#K!+ z(oEu*%HLJD!mXAXvO$+}+f-?*ZhKX@_m3>i;1=DxHS1T7hdj0p^o}{04zk@JF4<>D z6YKgCI`%ss_xqEL)Xp=GK3R<^zTQ`VbD_;`7~;;mgoK=9tPF`6dOTgh(^~UfZgzQ; zX=~GM0SatttidHy_3x0-$1WcPj}<=-LYL0rtcemY|1x(>NjN`s(>`nw&qQq#8{`nw z90<>Q#=`1#O5@;lwx~Htp|JiPHfCh(bNH0P6s9U}tjgx2;G^Za>0i+E`|OAOsU9E^ zd2SvS5XTg+NM%Ld{vl#Y?1{?B5XM9pPS6<~oqFKwa`+q#!v4g(tWIAaWya268bodI zgi7(q+WU^Vn=T$3?U_zXY4`F+9o4m#>>C2mc-X_@y@)FsfxmYyRxI{r$}CNlmH3`Z zWOByg1k!56xn(3iVm~x*R=aMy-~B^A>D;_04RSck-Q+o>%W#W$Hm>E%XtNj~Pp8Rj zx+rdji_A7y_mgU7W_O785k5h`L6aP+zM7B{2oF^Ijh@!MZ(SVXR<^!9f_G3DJk9SA zn+}w?u!8stL>J8Ot`ib`#D`k0u{X)$=Il&7d=TQI?9;|3p4Zq3dk0B;$o`dT>n*<* zP%BiMRC!9hq4*?F9b!<@k~W@NE3RYQinATQEm^z7%0&C=G9<_lz?vMul?tCA`RZfR z6b%e4zwN<#BeVe32h*5X`GdZ*zw?&^>OZr{*eH4HRTAW1^-MqB%--M3-rme!Z8QI! z=WV}#^E9(FG^P!h_Pv$$+GfAKeVeVHhjP{hP}U`HZxk^RwlF@KorTnE9 &hO77(3Y>fJZ8 zjJM}{o0E#4L;&)eH0d>_zXDe^t!kTXq&Gh7FNNNP4vAkaT(!LaZ5%MQ)r=Xq343F@ zMdjv@w|J|)w|s8CT|3EZ@ 5;n8 ze=VMw`UBlDJsY!Tu7R{H8B{joX1sEJ!utZrj~BO=Ob1KQ^4(uCwN;;#%>^!;Rha7i za>tch!Q}6j#xQW?ChPUcrSU!xubMnD&WK^lg4d{4EfvHF8#j1=>1jP}OfS{ mWEORJZ^F zqq$4gkv=JxE)f;+E$r)Yu919)_&Qhmav|b=49Q?{ByrjXo85L>y8-a0rJ&e2=tJl2 z_OUJR3}e(4#!MJR`xvSd^sB>OIENl>byXSj;pO8|u9sRFdj#IyY1mffS4yy27>qrF zu7$Q -u|m6BS; zIx1FW0;3Yqh)IQsF1gzY%GhXb(yw#nLUqECVEY>O@SwJBB&yLds)9zUlkSHC%Quvv zi7d+JIu8D~5Tg)3Hnuf=SB9@7EN6%~#snniGv%71W%wIPb(ONy6?p95(5dFkdZ0%K zD+hE;jN!2(wNNE&Q4m%NqV<))`6nDqc|4>+*1<*4UCeY@ZIL8#z<{A+nrdBlb^A!E zaI`@v6C03|pR;UPCBz@mmJRx*&QH+p)sidWD|1_vyx}t%Yjo@{0^=zM*0Szj!B9Nx zr+tS+$7~+x<5(UEbNSECs^?nsM1i!#5=Ml37zjo2Pr2W{Pc@0AcC#DQs5(HZz=dCT z@)jRdR|$LJti|w!%6C^zrUr>3c8-t-JJL$uJz=Q}2_Wb|heCoxLu}G}0&GW#CsTO6 zJRRC+ZeAcv4iva#yLOT&vYEjiF>BXpNpVJTu(wl1Lxd k1k zR@KGdQgtO+%g~c)jUVD~b>r-MMx>u=p0r!Z&weR>+=t)_UL>U{BIFAh0|`Fwu@l1; z^F <9VNm~a$ z4{;_=t!``9FFLSu7Y|rHt_MgIWmV@=&;|h@wX7FXstp!GD7_Ok0MmZ*h+TVOSyUJl z$lAlKrGF?IwZ+OcbSXw&@ymCtmG+`wARtU%2lxnnj)|~t;itF}hLE4#fB#@sr-^@4 z)znp1#b*HeO`8ck!|{rlrs%c0Ju|Wq(pN-_oP|+WeijEp9co^Z^)AxFA?+)1Ccm z-2Lwz5wOpPf7)(T(||9?3-?K=ShaB9VpvdY!^b?uqmfT2n0|-WBg<&=R7&C*89kxr zW{YXvrPJdVB1~Dic-FAx!%j%j`kGVm#|xwW4ZHCVkjZ$8E%zYzZo0)q=v{DnH~|=m z54-J1UPSj$RBzN9 By1Ek|fHL(i92Nj$s28Q7Ug3l-+jy@4-MoNP_{ ztvS4+%54w-bLp$~Pua|p;q6cLPvnK&Whsv?R>2HY->|CdujsN)jbeQasO%{ohdQ-; zibWb)0Kh&B_@b(VXq&Q}5MWN{%khP%pAsW+LRfVZSTkdZMLYkx20#G&Uk=&W*gN_Q z*N9S&i!~&7O}+c*wmA7(IcW8O&ZYXWWURUiCpGk-r5T!p6yVJ1DR6J44 F=y+KT5Z6%sS-OTPoYQ4`Ph6ckaW(4mWLpCC0QR>;Y>a5#sehr zTmR4}#dy8c7qxFM^!4xo67&>cuPe-Jp+Mu!HDuG~!Mk+rjIkGORuzqe!}4jxHui;L z!2BBr_w^S`P?%+L^XdH0b@<%I@K>RtO?N`M{YOk2qG`^`7{gnsHej+rSv0?8jY _8-qLt^Q0A)MzyNeBwnDxzD3*Pe4Rwz>}7tXQ!pmDthF=$!|j3 z{f@ouB46E|e?+9am6K#Zf`V`XuJL}eC`|5BZM4yvwk7#pykzhxW`n!l5#ol>#+!vn z^vUOikMhQjj8*JgM^6w%w!50Js+#+O%QL -KmcaL$sU*aLzmd5#ob?DWV7Oo(9!&NJue z_FxvRs*d+Y-Ap3ShzYd%swXa()VPsc_O8{!b1i3h!YVaqm)o_0$AjxlX$PAu5~bES z?g7H~p(FUJt`NnM&_mopaPLl5);kqLLd?86bi1tn2H>r%wchY2SxpQQ_kI3?`9bIs z5-O`L3y%AOZdV0tGiX_Da<6&|DI%_bB`+7R0#e%R_2#4N9qMqjGXKKRYYKCtWlV=t zbZXv5P7kPPc)`P}io5h%99?0AS8e%bOh6bgMqd+PyU}mqi?D6GnF+)BqNw~hxrB|F z+h@@LYBElFG?A)S4O7C?p#@*#GTA$gN>n#<;U9gQFLAI!^-*zraoG)9< r#E@8Mk&T6gE^^u)-Gk?KG6U$aXsss8Xc zMx`8o$r#j&---8 G}k5tAyvaW5qo*9zn9OiAa(eUzW(DyH#_cx z=M-mNG_Jb&t^7~K9>F}`r}!k2a-n-}Wsv3mCkzpfL!mZRjG78J6K!UJ^toxh0mNRL z;z+wURGu5r7DWP#y1yN29#*~yc&N7)^xjr(=dpmsSA !#{-Q?E_f6(>ZuZGu`t9qDY)#>8{VXJP)cbDs<({X{)H|>8lhc&=k2^ z1Gj~*o8~RdW6}7P CDjw{84^0i$^JoeKZ(UlQFWB#C?x zyJ?#f$xqZB$#WL`8}g9H2Gc)M?+~cOol4xrP-Sg>Aewo%)j~JE##*(_i6~jG3mO$6 z+moP3B4k|hK> b-e+-|6aL9d^uz_#?V825mvQ> zJ#!$fEPd|rfVw^$d<^i+KzygU$ePsgHt~t*O}5E}7_{4Hwbe59{!}v)z*V$wpZ4_g zP(rS)3;>v@SS@K95z+~^tqb~vn&&$fSxTR>b<^YjPXLY*aqUK3rBSCf7>ru5&02@r zr^0yOi*G!OcJAOayK_!sZ1&sP*z&vGevo{YctKczXRI0dl*w#md%P+`+WP(-3C{1{ zd3Walyc@R^c#BYd-p-0+EQrOv8uZ?~e5NkPQi2Py1_o*6>ua~J4uapR)d6HGyE0$! zxm{S^o7y{^$8MJoWM}i~0=j^~VZz8@F=P3a;a5_5Y%v=uHx< $tT|fTSL5kleW1mgr1rytXzpJ zd7V!mkk#PY>Kcp!z b&w<+ICbRYp)-fh zw5MLtitmwcgaMx$k?zafohkcY1X0$ctYP-8>i<>{AJO3*51?&nr `b*{c%TPdri4`S Xj?~%FwS_W{U;PH{e1|xPMolqhGTS zs8*qOL%(LB)T`P* 4@10muhFabSGn0f2cwmJ^F2E@GPXz@$$mry(3BEaRAA+Q_<0SIPv;B&uYWWn zjDvpM@IU-q23wl0cKH1@z)s|rMl`MEvTBJ1IJ-I xgea!5Ql?7jR|DA-uvP>;y!n||d(9W*pRuyJSfjPOgeO}b{?t67;jh%EeEczA+z z-)r03TAESc5A;4kyd @%&x+Fp@ 3|_Ym5` f-4ZR EgqJ zIGN;0i4KzrOd#5iO@^3%pp)V}ai8!oR3;@A4?|0LNJF^J>@)Zct}3iSxzgBbU^+A* zT`}Tex_Flqhun2O_!o2sK~X1PXKBODEbT3cE)guXR-oH}?bgh7Mu)M+a07ejW0?P6 zSdOb}K$~*bTP)QKQM2hdqu1cG#=q98L9Uue!<&0iH1a=4q{-Z*uiv!M;jCekMm_+} zKBL# T`k228KvT1>7Y-$gQeazrkqMR2fXjEFmwv zB{)pA;5<<7Gtx~PH$@sk=*r+&-M^U(SaR8`OVZ~oMH9~dN73IeeR|%Di(XDc;s{bH zeDcmG-JJ|$FtJD&bDqQe)=gVmceFVz9;*lcKd4CT6x{?J#dbTNomW^=QdV7Sud&w- zr~onT6#gS+bJj|3R;fEAG5_1b>>1K J{;;}u@ zaDrQ_3r+s!+i&=RP4#S!6}m!VAt4%&26c6nvTHwAP!C56=2vECuaZ+FU9~6s5c~Bk z(m?B3tu{ACp+K-s9-AQ+2;{dC_6a |bW9!`wz5}O1Fs;cBHo~*U*SQeREJIg++Y~H%1c3b@GDJ`Q6EVs+)L9cZ- z_tbu9|FGoUHJw(E$;H%sVjnNm`|BX?kJF)%nQoxt)|=>f6O0ok^#gQx094#7+5~b( zH4m~Mph1QE?gAQ02Bu2?M;bnB7pE{1zHLv8W&;sbx+>j&BVz5+>H#8rdbYVBPQ>oc zjW-ZsWJHibu@=G0!cF(7uXg ^@Zr#RZ~ha+d50JlD)l<8oORYQzzN$+s=4cy-6&2U_FNa|2h|^zzPsi$ z {dG?vnrsg!~I-Br@q?xGw=vFwf3i%zp}df z*`x isQVkjon%{0CSWpt36W1B9OMMej5D-~eedY7qCeepgpR;;B&MTY zp(7q1V`HtE`&&cw(k3D5W4|g@$rQ`p&(siO>Z gdxX^E9`sLo9rYJb8j}ClaGI(T9W^zSc_~ zGj1FvPxH9W 5 z`zR$Y=L|Sb(hlAYy5NmZ203a2PLChgAskI6cab}Jx7)#f&4YdO;YP8aV{3&X2%CAe zLsMVfh{)~In*Y 7)osFw#qJl>mL0FHJG2nr^YNG@6zSr|Vg-2CV z3spUDUiUk?`@J_azq?=ArcGsF!~M-}t|e>})g$y%dbPw(>HoCj@0u*1 DKBr`-jiGFa{iNjap8rS zlg3fdbUX^0g=<@eY`A(6QwSho64t^ax#440Zw`a zwQw>*I^@R*z1t6}`)Hgu{c%4hqgAL5`nC#j$(%%GJ}`*Mi}CXj8p_GU>#;zbJq&T< zI2qGFiSx$dR)Mo#D{FD+@7p=)7EU-JVG1X{TLCiH17yimp|EK(CtYIz>}nwrAWEU$ zavAD9duxDHGz#R6DwJfX!r@QrfD{4A+!R&-L_h3MJps{EbvN?IdZGv%1u4NgNa;D) zT?Fe&Aa+9a1m2 K4Q8Ek%*7tPJlIamV@bB~V;D1hd4CaNxlYhH5ws{ee^cpFzC; zeSm)O0vW-d0SjJ(f-V&k-hMt#Geb)=)ZYpOMLfrYu;yyCieu37-Xt94ZnFV-TNkGB z9f-O&5C?rc)VJ&bs9P%zcN-ePE3YWrbl m zdvR0j=PGY+XwW)rz-K2iJO~J{p`b*sUTCiGLh+joQ1~SPZt!R*dK9Bg4n0Z?8-U&h ze+TTZ+y=^?zMyXXAQB+N!Hhj{;Lx=E1r53!>7Jjf7^OfMSnp77x;h W5(FALe zkh+;XaHs|+fdv|u2oQ(TRYL_{AFc(8S;UTSAAE$9Y1>D0{?m)wI2qV|Kj-I{pLrq_ zOy7zDU^Vu6o(KvU3Pu(hjt%a7)*TA`2e_Pl!ugY&L`HaU66kxw6$;)cQC(1wzid$O zCN>EeCYjI=JT{&o{8!BjRhXd%r3AENiJ)|AI4J$_3>FP&K+}dm58qpZM*8}3O?ViU zo_4W2ZWnvpzufNjv;KAWm-v_O&-m9%T8DqB&+@M>PxzOGJnmmNf6`q3RU5B$@~_$i z;_|N(E7fQE*8pfx-w!zV48hrd2!ete(7s4dQTn5=wF}9b2!kMg#RW7bX(?2h6BP*t z{NKR*VKqo@4F-tC luPUBNS?g9T^-&jfGbm4-Je= zB4mkZWfbBTDrB^2#dt dU zG%&){i}4~%k&ZB>VVKe|OsP+WDa8mB5T=xlFf~Sbc9>G18K!>p1wZ*eBb3%DLP6_A z`yc>c#VGva*P6sl=_f$5$)9L;cyHjO%Q<9o5BlZZinh4}H1Y0^n-G_PzZxOF&R^>Y z@ps-(p9=AD9{}#rK%C;Q<^c2k`#}!zx`EFv3@zb*0O;~q%@{j=y TM=b_=r3;yio5h>sI{z|Kk|?4xKlJzD(aP zc(;6^%yzIT(m$AQ?b#c2E8UV+mM$}&%hK4*`Tz%R9C9sfsF{h$sVSY8HUbLA?R-yu zG@_--uFJ1VJL5{H OrE_kk4k_X4#>GQK7V1MJaD}9{Yut1jYWLeX}P2@uehkd(Xm|8C+*w^U(4;U zw{^AWrxay7^5sXP9`= D0k-{ zb|jak$e%C$D4I^DajRkz5@bD6kCYPT$0o(nSUR(Ko^#%|qzYPzC)k`$xw=|%?Q+D^ z?wAB5N9^#!pBcuEm13r^h^BATpbKHl2j1v^%ku4;==$8;5|Jb?mu^Viu(DveBWe#l zPj_z;N8(1ZNXhA}EZIeO(aJ<;f-|9H7D$dwoCV4N%k=c@G-Jo1SV07_C_E?6JG7=+ z+P$~*AaqY#c5ud?8M~uPqv=~Tdil&5MlWJ!EsdtpRJSvELNS$nhOtKdub0>y*67D< ztmz_a(&t>ZMzkDYckRFPY8|FG!o^zI=6DV+dmq}m4>DU9>o!m8a@{rg{#h>&xP`-rh$oU&2S`+C1+r{v^Ok{4lA$m5d@$WC2-6 z%1ITeCD%<|O#Mw0O^Zw$P1{XhnNFI{;di)uRTx)_11v`VA9-WRhRuO3KAwM1EM6Iq zDH7Spx*8-UrKhLSBv~JVXWf(6XUG;kvJU)&Mb=6dy >~nNU&TbXHU(IOF0H65;|Zx+-Wee`3cK+Ny}htDyPRO*322 z#BBpE+W`{~T;?H$ZD9*snHv|d1+L7E3p! %t-X$Om{CP5dQz}%Y5mj3||J*5Qzc${NkWME+60b(%* zIR+*MMj*@rViuqX6A&{(FaQuJ0J#7F0000100000-`2}900000)P}@x00000;6KC} zc$`I!F$%&^5JVs03lbzI8X>iv0Sg5$AUT1p76BoULa;Ms8mnlbl?U@WqO)JY9cI}- zGk;l^K`;OZp*Ru~=R`=EF(mPb#fVJ3^zU5mA8_QrQ v2FB-meQUdlqpZ~PVchm3ObWO~bxprF>Ooc#Nq>Tj+UC>vyV=cF){Q#uj H6kY%TJzsGD literal 0 HcmV?d00001 From 7d025156d23c35bd3d581d3e7b287d513ad8e7ee Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Fri, 23 Jun 2023 00:29:03 +0900 Subject: [PATCH 41/91] Update image.js --- src/components/thumbnails/image.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/thumbnails/image.js b/src/components/thumbnails/image.js index c1cb663c..68325c60 100644 --- a/src/components/thumbnails/image.js +++ b/src/components/thumbnails/image.js @@ -30,7 +30,6 @@ const Image = ({ ); } - //Currently not implemented const external_video = src === ID.EXTERNAL_VIDEOS; if (external_video) { From f43aa0a4cad1b48a43dd23a9e16a7581e3d4cde5 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Fri, 23 Jun 2023 00:29:59 +0900 Subject: [PATCH 42/91] Update builder.js --- src/utils/builder.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/builder.js b/src/utils/builder.js index 2cc7a6d7..c4f0cf40 100644 --- a/src/utils/builder.js +++ b/src/utils/builder.js @@ -222,7 +222,6 @@ const buildThumbnails = slides => { timestamp, }); } else if (src.includes(ID.EXTERNAL_VIDEOS)) { - //Currently not implemented result.push({ id, src: ID.EXTERNAL_VIDEOS, From 74bccd185536accb411ffbd528c00e776987d2f7 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 11:25:30 +0900 Subject: [PATCH 43/91] Youtube control on --- src/components/external-video-player/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index f03a67b8..84ef16da 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -72,7 +72,7 @@ class ExternalVideoPlayer extends Component { autohide: 1, rel: 0, ecver: 2, - controls: 0, + controls: 1, enablejsapi: 0, showinfo: 0 }, From cca9953017ca5b92286abadd4fe1771e97502170 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 11:45:24 +0900 Subject: [PATCH 44/91] disable volume sync in synchronizer --- src/utils/synchronizer.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index 382258f8..a33168f7 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -53,7 +53,7 @@ export default class Synchronizer { this.init(); } - +/* syncVolume() { const volume = this.primary.volume(); const muted = this.primary.muted(); @@ -62,7 +62,7 @@ export default class Synchronizer { this.externalVideos.handleVolumeChange(volume,muted); } } - +*/ handleUpdateTime() { const currentTime = this.primary.currentTime(); @@ -92,7 +92,8 @@ export default class Synchronizer { this.secondary.playbackRate(playbackRate); }); - this.primary.on('volumechange', () => this.syncVolume()); + //do this at external_video-player/index.js + //this.primary.on('volumechange', () => this.syncVolume()); this.primary.on('timeupdate', () => this.handleUpdateTime()); From e81fe8b1417f8f996e58c18387b6275270580dde Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 12:26:44 +0900 Subject: [PATCH 45/91] [chore] reorder --- src/components/external-video-player/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 84ef16da..48d465c4 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -226,11 +226,10 @@ class ExternalVideoPlayer extends Component { const { playing, playbackRate } = this.state; this.time = player.primary.currentTime(); - - let primaryPlayerPlaying = true; this.handleVolumeChange(player.primary.volume(), player.primary.muted()); + let primaryPlayerPlaying = true; if (this.time === this.lastTime) { primaryPlayerPlaying = false; } From 978f9f855ca80f37ad9247cec0c16bed27605bd5 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 12:48:11 +0900 Subject: [PATCH 46/91] better synchronisation 1. remove volume sync and rate sync from here. (-> external-video-player/index.js) 2. more precisely distribute 'if (this.secondary)' into the code --- src/utils/synchronizer.js | 59 ++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index a33168f7..c4f9b8ad 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -66,33 +66,41 @@ export default class Synchronizer { handleUpdateTime() { const currentTime = this.primary.currentTime(); - if (this.externalVideos && this.externalVideos.time !== currentTime) - { + if (this.externalVideos && this.externalVideos.time !== currentTime) { + // only this one works, but volume, muted, rate are not tractable from here by this way.. this.externalVideos.time = currentTime; } } init() { - if (this.secondary) { STATUSES.forEach(status => { this.primary.on(status, () => this.status.primary = status); - this.secondary.on(status, () => this.status.secondary = status); + if (this.secondary) { + this.secondary.on(status, () => this.status.secondary = status); + } }); this.primary.on('play', () => this.secondary.play()); - this.primary.on('pause', () => this.secondary.pause()); + if (this.secondary) { + this.primary.on('pause', () => this.secondary.pause()); + } this.primary.on('seeking', () => { const currentTime = this.primary.currentTime(); - this.secondary.currentTime(currentTime); + if (this.secondary) { + this.secondary.currentTime(currentTime); + } }); this.primary.on('ratechange', () => { const playbackRate = this.primary.playbackRate(); - this.secondary.playbackRate(playbackRate); + if (this.secondary) { + this.secondary.playbackRate(playbackRate); + } }); //do this at external_video-player/index.js + // (not by passing props but by getting the values from primary player directly (dirty..) //this.primary.on('volumechange', () => this.syncVolume()); this.primary.on('timeupdate', () => this.handleUpdateTime()); @@ -111,19 +119,21 @@ export default class Synchronizer { } }); - this.secondary.on('waiting', () => { - if (!this.synching && this.status.primary === 'canplay') { - this.synching = true; - this.primary.pause(); - } - }); - - this.secondary.on('canplay', () => { - if (this.synching) { - this.synching = false; - this.primary.play(); - } - }); + if (this.secondary) { + this.secondary.on('waiting', () => { + if (!this.synching && this.status.primary === 'canplay') { + this.synching = true; + this.primary.pause(); + } + }); + + this.secondary.on('canplay', () => { + if (this.synching) { + this.synching = false; + this.primary.play(); + } + }); + } // IMPORTANT: Blink holds the secondary media down while the document // page is not visible @@ -131,14 +141,17 @@ export default class Synchronizer { document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') { const currentTime = this.primary.currentTime(); - this.secondary.currentTime(currentTime); + if (this.secondary) { + this.secondary.currentTime(currentTime); + } } }); EVENTS.forEach(event => { this.primary.on(event, () => logger.debug(`primary ${event} ${this.status.primary}`)); - this.secondary.on(event, () => logger.debug(`secondary ${event} ${this.status.secondary}`)); + if (this.secondary) { + this.secondary.on(event, () => logger.debug(`secondary ${event} ${this.status.secondary}`)); + } }); - } } } From b76535d7a138bf3a57dc3e00b5ac75f69c36d7f9 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 12:53:53 +0900 Subject: [PATCH 47/91] better synchronisation Remove primary player's value from props that will be passed to the external video player (because it did not work by whatever reasons...) --- src/components/player/content/index.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index 7de3f57a..3f2293a3 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -60,16 +60,23 @@ const Content = ({ const url = video.url const events = video.events; - let primaryPlaybackRate = 1; + //let primaryPlaybackRate = 1; + //let primaryPlaybackVolume = 1; + //let primaryPlaybackMuted = false; /* if (player.webcams) { primaryPlaybackRate = player.webcams.playbackRate(); } */ // Use primary player for timing, instead of webcam player (no difference?) + // -> in the end this way to pass props did not work... +/* if (player.primary) { primaryPlaybackRate = player.primary.playbackRate(); + primaryPlaybackVolume = player.primary.volume(); + primaryPlaybackMuted = player.primary.muted(); } +*/ return ( ); From 6d23564319fc3ff66462032010433a8812beb4e4 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 13:00:34 +0900 Subject: [PATCH 48/91] better synchronisation obtain the rate, muted, volume directly from the primary player and pass them to this.state. The ordinary way to pass them as props did not work....? --- src/components/external-video-player/index.js | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 48d465c4..0e03ef6e 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -131,16 +131,16 @@ class ExternalVideoPlayer extends Component { } - setPlaybackRate() { - - const { primaryPlaybackRate } = this.props; + setPlaybackRate(value) { + //The original way to get the rate from props did not work...? + //const { primaryPlaybackRate } = this.props; // Rate depends on primary rate player - const rate = primaryPlaybackRate * this.lastEventPlaybackRate; + const rate = value * this.lastEventPlaybackRate; const currentRate = this.state.playbackRate; - logger.debug(`external_video: setPlaybackRate current=${currentRate} primary=${primaryPlaybackRate} lastEventPlaybackRate=${this.lastEventPlaybackRate} rate=${rate}`); + logger.debug(`external_video: setPlaybackRate current=${currentRate} primary=${value} lastEventPlaybackRate=${this.lastEventPlaybackRate} rate=${rate}`); if (currentRate === rate) { return; @@ -222,18 +222,19 @@ class ExternalVideoPlayer extends Component { } orchestrator () { - const { events, active, /*getCurrentPlayerTime,*/ primaryPlaybackRate } = this.props; + const { events, active/*, primaryPlaybackRate, primaryPlaybackVolume, primaryPlaybackMuted*/ } = this.props; const { playing, playbackRate } = this.state; this.time = player.primary.currentTime(); - this.handleVolumeChange(player.primary.volume(), player.primary.muted()); - let primaryPlayerPlaying = true; if (this.time === this.lastTime) { primaryPlayerPlaying = false; } + //this.handleVolumeChange(primaryPlaybackVolume, primaryPlaybackMuted); // did not work...? + this.handleVolumeChange(player.primary.volume(), player.primary.muted()); + this.lastTime = this.time; this.primaryPlayerPlaying = primaryPlayerPlaying; @@ -278,7 +279,8 @@ class ExternalVideoPlayer extends Component { } } - this.setPlaybackRate(); + //this.setPlaybackRate(); + this.setPlaybackRate(player.primary.playbackRate()); } From 686c5ef80d5c6980c0165abdc78e574767b5cd70 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 13:52:45 +0900 Subject: [PATCH 49/91] better synchronisation comment why passing volume, mute, and rate by props does not work. --- src/components/player/content/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index 3f2293a3..b23b3494 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -69,7 +69,8 @@ const Content = ({ } */ // Use primary player for timing, instead of webcam player (no difference?) - // -> in the end this way to pass props did not work... + // -> in the end this way to pass props did not work, + // because player/content would be rendered only once in the beginning. /* if (player.primary) { primaryPlaybackRate = player.primary.playbackRate(); From ebf1beb59f880a6a2fd5dae4d74386048a385009 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 13:54:24 +0900 Subject: [PATCH 50/91] Update index.js --- src/components/external-video-player/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 0e03ef6e..585e35ba 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -132,7 +132,8 @@ class ExternalVideoPlayer extends Component { setPlaybackRate(value) { - //The original way to get the rate from props did not work...? + //The original way to get the rate from props did not work, + // because props will not be updated after the initial rendering. //const { primaryPlaybackRate } = this.props; // Rate depends on primary rate player From 3a6d99c5b6dd367c9f64c9960b6f8e4dca92e21c Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Wed, 28 Jun 2023 15:36:34 +0900 Subject: [PATCH 51/91] [bug] second player --- src/utils/synchronizer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index c4f9b8ad..098feb1f 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -80,8 +80,8 @@ export default class Synchronizer { } }); - this.primary.on('play', () => this.secondary.play()); if (this.secondary) { + this.primary.on('play', () => this.secondary.play()); this.primary.on('pause', () => this.secondary.pause()); } From 249f41451d6faf8a8bad99b7cfcc7ef1c3391646 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Wed, 28 Jun 2023 22:06:55 +0900 Subject: [PATCH 52/91] [fix] hitchhiking on thumbnails Since the externalvideo component does not get rerendered in the current implementation, I move all rerendering process into external-video-player.js --- src/components/player/content/index.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index b23b3494..c71a4b40 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -57,8 +57,14 @@ const Content = ({ return } - const url = video.url const events = video.events; + + const videos = external_videos.map(video => { + return { + url: video.url, + time: [video.timestamp, video.clear], + } + }); //let primaryPlaybackRate = 1; //let primaryPlaybackVolume = 1; @@ -82,7 +88,7 @@ const Content = ({ Date: Wed, 28 Jun 2023 22:15:57 +0900 Subject: [PATCH 53/91] [fix] hitchhiking on thumbnails Look for the video url for each cycle of orchestrator... --- src/components/external-video-player/index.js | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 585e35ba..881b29ad 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -49,6 +49,7 @@ class ExternalVideoPlayer extends Component { errorPlaying: false, playbackRate: 1, volume: 1, + urlPlayed: "", }; this.opts = { @@ -97,6 +98,7 @@ class ExternalVideoPlayer extends Component { this.orchestrator = this.orchestrator.bind(this); this.autoPlayBlockDetected = this.autoPlayBlockDetected.bind(this); + this.whichVideo = this.whichVideo.bind(this); //this.dispatchTimeUpdate = this.dispatchTimeUpdate.bind(this); } @@ -222,8 +224,13 @@ class ExternalVideoPlayer extends Component { clearInterval(this.timer); } + whichVideo = (videos, time) => { + const found = videos.find(video => video.time[0] <= time && video.time[1] >= time); + return found ? found.url : ""; + } + orchestrator () { - const { events, active/*, primaryPlaybackRate, primaryPlaybackVolume, primaryPlaybackMuted*/ } = this.props; + const { events, active/*, primaryPlaybackRate, primaryPlaybackVolume, primaryPlaybackMuted*/, videos } = this.props; const { playing, playbackRate } = this.state; this.time = player.primary.currentTime(); @@ -243,10 +250,14 @@ class ExternalVideoPlayer extends Component { this.autoPlayTimeout = setTimeout(this.autoPlayBlockDetected, AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS * 1000); } - const index = getCurrentDataIndex(events, this.time); + if (active) { + const currentVideo = this.whichVideo(videos, this.time); + this.setState({ urlPlayed: currentVideo }); + } - logger.debug(`external_video: player time=${this.time} active=${active} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}`); + const index = getCurrentDataIndex(events, this.time); + logger.debug(`external_video: player url=${currentVideo} time=${this.time} active=${active} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}`); if (!primaryPlayerPlaying || !active) { this.handleOnPause(); @@ -287,8 +298,8 @@ class ExternalVideoPlayer extends Component { render() { - const { videoUrl, active, intl } = this.props; - const { playing, playbackRate, muted, autoPlayBlocked, volume } = this.state; + const { /*videoUrl,*/ active, intl } = this.props; + const { playing, playbackRate, muted, autoPlayBlocked, volume, urlPlayed } = this.state; return ( @@ -306,7 +317,7 @@ class ExternalVideoPlayer extends Component { } Date: Wed, 28 Jun 2023 23:10:48 +0900 Subject: [PATCH 54/91] [fix] hitchhiking on thumbnails Maybe not necessary.. --- src/components/external-video-player/index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 881b29ad..203d4b6a 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -250,13 +250,13 @@ class ExternalVideoPlayer extends Component { this.autoPlayTimeout = setTimeout(this.autoPlayBlockDetected, AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS * 1000); } + const index = getCurrentDataIndex(events, this.time); + if (active) { const currentVideo = this.whichVideo(videos, this.time); this.setState({ urlPlayed: currentVideo }); } - const index = getCurrentDataIndex(events, this.time); - logger.debug(`external_video: player url=${currentVideo} time=${this.time} active=${active} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}`); if (!primaryPlayerPlaying || !active) { @@ -301,6 +301,11 @@ class ExternalVideoPlayer extends Component { const { /*videoUrl,*/ active, intl } = this.props; const { playing, playbackRate, muted, autoPlayBlocked, volume, urlPlayed } = this.state; + if (urlPlayed == "") { + const currentVideo = this.whichVideo(videos, this.time); + this.setState({ urlPlayed: currentVideo }); + } + return ( Date: Thu, 29 Jun 2023 00:09:29 +0900 Subject: [PATCH 55/91] handleUpdateTime does not really work now.. --- src/utils/synchronizer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index 098feb1f..a80f20fc 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -103,7 +103,8 @@ export default class Synchronizer { // (not by passing props but by getting the values from primary player directly (dirty..) //this.primary.on('volumechange', () => this.syncVolume()); - this.primary.on('timeupdate', () => this.handleUpdateTime()); + // Actually this does not work at all.. + //this.primary.on('timeupdate', () => this.handleUpdateTime()); this.primary.on('waiting', () => { if (!this.synching && this.status.secondary === 'canplay') { From fc82edfe0495cb48a8a35a359d91c0038e9bfeeb Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 29 Jun 2023 00:18:15 +0900 Subject: [PATCH 56/91] refactor external-video-player Thumbnail hitchhiking started to work, but after jumping on a external video, you cannot use the primary player's slider to seek the video. But when the player enters external video from a normal slide, the slider works well. Obviously the synchronise function stops to work if the react-player is not re-rendered..need to find where the external video is synchronised with the primary player. --- src/components/external-video-player/index.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 203d4b6a..46a5b27a 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -226,7 +226,8 @@ class ExternalVideoPlayer extends Component { whichVideo = (videos, time) => { const found = videos.find(video => video.time[0] <= time && video.time[1] >= time); - return found ? found.url : ""; + //return found ? found.url : ""; + return found ? found : {url: "", time:[0, 0]}; } orchestrator () { @@ -254,7 +255,11 @@ class ExternalVideoPlayer extends Component { if (active) { const currentVideo = this.whichVideo(videos, this.time); - this.setState({ urlPlayed: currentVideo }); + if (currentVideo.url !== this.state.urlPlayed) { + this.setState({ urlPlayed: currentVideo.url }); + } + // This skrews up the playback (playing back and forth..), but should work in theory + //this.seekTo(this.time - currentVideo.time[0]); } logger.debug(`external_video: player url=${currentVideo} time=${this.time} active=${active} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}`); @@ -298,12 +303,12 @@ class ExternalVideoPlayer extends Component { render() { - const { /*videoUrl,*/ active, intl } = this.props; + const { /*videoUrl,*/ active, intl, video } = this.props; const { playing, playbackRate, muted, autoPlayBlocked, volume, urlPlayed } = this.state; if (urlPlayed == "") { const currentVideo = this.whichVideo(videos, this.time); - this.setState({ urlPlayed: currentVideo }); + this.setState({ urlPlayed: currentVideo.url }); } return ( From 73a312da595c0548ccd288a0b9d075ce4743b64d Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:05:03 +0900 Subject: [PATCH 57/91] more simple implementation --- src/components/external-video-player/index.js | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 46a5b27a..e4ca4ced 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -19,7 +19,7 @@ const intlMessages = defineMessages({ }); -const SYNC_INTERVAL_SECOND = 5; +const SYNC_INTERVAL_SECOND = 1; const AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS = 5; const ORCHESTRATOR_INTERVAL_MILLISECOND = 500; @@ -128,7 +128,7 @@ class ExternalVideoPlayer extends Component { getCurrentTime() { if (this.player && this.player.getCurrentTime) { - return Math.round(this.player.getCurrentTime()); + return this.player.getCurrentTime(); } } @@ -143,12 +143,11 @@ class ExternalVideoPlayer extends Component { const currentRate = this.state.playbackRate; - logger.debug(`external_video: setPlaybackRate current=${currentRate} primary=${value} lastEventPlaybackRate=${this.lastEventPlaybackRate} rate=${rate}`); - if (currentRate === rate) { return; } + logger.debug(`external_video: setPlaybackRate current=${currentRate} primary=${value} lastEventPlaybackRate=${this.lastEventPlaybackRate} rate=${rate}`); this.setState({ playbackRate: rate }); } @@ -197,8 +196,14 @@ class ExternalVideoPlayer extends Component { } handleVolumeChange = (value, isMuted) => { - this.setState({ volume: parseFloat(value)}); - this.setState({ muted: isMuted}); + if (this.state.volume !== value ) { + this.setState({ volume: parseFloat(value)}); + logger.debug(`external_video: VolumeChange CV=${this.state.volume.toFixed(2)} NV=${value.toFixed(2)}`); + } + if (this.state.muted != isMuted) { + this.setState({ muted: isMuted}); + logger.debug(`external_video: muteChange CM=${this.state.muted} NM=${isMuted}`); + } } @@ -212,6 +217,7 @@ class ExternalVideoPlayer extends Component { // Seek if viewer has drifted too far away from presenter if (Math.abs(this.getCurrentTime() - time) > SYNC_INTERVAL_SECOND * 0.75) { + logger.debug(`Video synchronised! ${(time - this.getCurrentTime()).toFixed(2)} `); player.seekTo(time, true); } } @@ -225,13 +231,12 @@ class ExternalVideoPlayer extends Component { } whichVideo = (videos, time) => { - const found = videos.find(video => video.time[0] <= time && video.time[1] >= time); - //return found ? found.url : ""; - return found ? found : {url: "", time:[0, 0]}; + const found = videos.find(video => video.timestamp <= time && video.clear >= time); + return found ? found : {url: "", timestamp: 0, clear: 0}; } orchestrator () { - const { events, active/*, primaryPlaybackRate, primaryPlaybackVolume, primaryPlaybackMuted*/, videos } = this.props; + const { /*events, active, primaryPlaybackRate, primaryPlaybackVolume, primaryPlaybackMuted*/, videos } = this.props; const { playing, playbackRate } = this.state; this.time = player.primary.currentTime(); @@ -247,32 +252,32 @@ class ExternalVideoPlayer extends Component { this.lastTime = this.time; this.primaryPlayerPlaying = primaryPlayerPlaying; - if (active && !this.hasPlayedBefore && !this.autoPlayTimeout) { + if (/*active &&*/ !this.hasPlayedBefore && !this.autoPlayTimeout) { this.autoPlayTimeout = setTimeout(this.autoPlayBlockDetected, AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS * 1000); } + const currentVideo = this.whichVideo(videos, this.time); const index = getCurrentDataIndex(events, this.time); - if (active) { + //if (active) { const currentVideo = this.whichVideo(videos, this.time); if (currentVideo.url !== this.state.urlPlayed) { this.setState({ urlPlayed: currentVideo.url }); + logger.debug(`external_video URLchange ${currentVideo.url} -> ${this.state.urlPlayed}`); } - // This skrews up the playback (playing back and forth..), but should work in theory - //this.seekTo(this.time - currentVideo.time[0]); - } + //} - logger.debug(`external_video: player url=${currentVideo} time=${this.time} active=${active} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}`); + logger.debug(`external_video: player url=${this.state.urlPlayed} time=${this.time.toFixed(2)} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}, events=${currentVideo.events}`); - if (!primaryPlayerPlaying || !active) { + if (!primaryPlayerPlaying /*|| !active*/) { this.handleOnPause(); this.playerUpdateTime = -1; return } - if (index && events && events[index] && events[index].type) + if (index && currentVideo.events && currentVideo.events[index] && currentVideo.events[index].type) { - const {type, time, rate, playing} = events[index]; + const {type, time, rate, playing} = currentVideo.events[index]; logger.debug(`External Video Event: type=${type} time=${time} rate=${rate} playing=${playing}`); @@ -303,18 +308,14 @@ class ExternalVideoPlayer extends Component { render() { - const { /*videoUrl,*/ active, intl, video } = this.props; + const { /*videoUrl, active,*/ intl/*, video*/ } = this.props; const { playing, playbackRate, muted, autoPlayBlocked, volume, urlPlayed } = this.state; - if (urlPlayed == "") { - const currentVideo = this.whichVideo(videos, this.time); - this.setState({ urlPlayed: currentVideo.url }); - } - + logger.debug(`Rendered ${urlPlayed}. Note this shouldn't be shown frequently!`); return ({ this.playerParent = ref; }} > {autoPlayBlocked @@ -338,7 +339,7 @@ class ExternalVideoPlayer extends Component { onPause={this.handleOnPause} onBuffer={this.handleOnBuffer} onBufferEnd={this.handleOnBufferEnd} - ref={(ref) => { this.player = ref; if (!player.external_videos) {player.external_videos = this.player; } }} + ref={(ref) => { this.player = ref; }} width="100%" height="100%" /> From a62ad9b9f2bfec1a3d0f4b13beb3fbfafe88c4e7 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:13:52 +0900 Subject: [PATCH 58/91] more simple implementation --- src/components/external-video-player/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index e4ca4ced..d54e4220 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -260,11 +260,16 @@ class ExternalVideoPlayer extends Component { const index = getCurrentDataIndex(events, this.time); //if (active) { - const currentVideo = this.whichVideo(videos, this.time); if (currentVideo.url !== this.state.urlPlayed) { this.setState({ urlPlayed: currentVideo.url }); logger.debug(`external_video URLchange ${currentVideo.url} -> ${this.state.urlPlayed}`); } + // Check time consistency every ORCHESTRATOR_INTERVAL_MILLISECOND msec, and fix when drifted away too much + if (index && currentVideo.events && currentVideo.events[index] && playing && (currentVideo.events[index].type == "playerUpdate" || currentVideo.events[index].type == "play") ){ + const thisPlayerTimeToBe = parseFloat(currentVideo.events[index].time) + (this.time - currentVideo.events[index].timestamp); + logger.debug(`Player time to be=${thisPlayerTimeToBe.toFixed(2)} actual player time=${this.player.getCurrentTime().toFixed(2)} inconsistency=${(thisPlayerTimeToBe - this.player.getCurrentTime()).toFixed(2)} Type=${currentVideo.events[index].type}`); + this.seekTo(thisPlayerTimeToBe); + } //} logger.debug(`external_video: player url=${this.state.urlPlayed} time=${this.time.toFixed(2)} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}, events=${currentVideo.events}`); From 8a831859660c0480e271a4ba034b1a971c6d4604 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:16:00 +0900 Subject: [PATCH 59/91] Update index.js --- src/components/external-video-player/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index d54e4220..e3d72382 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -316,9 +316,8 @@ class ExternalVideoPlayer extends Component { const { /*videoUrl, active,*/ intl/*, video*/ } = this.props; const { playing, playbackRate, muted, autoPlayBlocked, volume, urlPlayed } = this.state; - logger.debug(`Rendered ${urlPlayed}. Note this shouldn't be shown frequently!`); + logger.debug(`Rendering ${urlPlayed}. Note this shouldn't be shown frequently!`); return ( -{ this.playerParent = ref; }} From eb2e5cb451c88574eb9f6357f2465c3cc3ba918c Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:19:14 +0900 Subject: [PATCH 60/91] more simple implementation --- src/components/player/content/index.js | 51 ++------------------------ 1 file changed, 3 insertions(+), 48 deletions(-) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index c71a4b40..1e15f4d0 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -36,61 +36,16 @@ const Content = ({ storage.cursor.tldraw; const RenderExternalVideo = () => { - const time = player.primary ? player.primary.currentTime() : 0; const intl = useIntl(); const { external_videos } = storage; - const currentContent = useCurrentContent(); - - if (!external_videos) { - return; - } - let currentDataIndex = getCurrentDataIndex(external_videos, time); - - if (currentDataIndex === -1) { - currentDataIndex = 0; - } - - const video = external_videos[currentDataIndex]; - - if (!video) { - return - } - - const events = video.events; - - const videos = external_videos.map(video => { - return { - url: video.url, - time: [video.timestamp, video.clear], - } - }); - - //let primaryPlaybackRate = 1; - //let primaryPlaybackVolume = 1; - //let primaryPlaybackMuted = false; -/* - if (player.webcams) { - primaryPlaybackRate = player.webcams.playbackRate(); - } -*/ - // Use primary player for timing, instead of webcam player (no difference?) - // -> in the end this way to pass props did not work, - // because player/content would be rendered only once in the beginning. -/* - if (player.primary) { - primaryPlaybackRate = player.primary.playbackRate(); - primaryPlaybackVolume = player.primary.volume(); - primaryPlaybackMuted = player.primary.muted(); - } -*/ return (Date: Sat, 1 Jul 2023 09:23:51 +0900 Subject: [PATCH 61/91] more simple implementation reverting the changes --- src/utils/player.js | 46 ++++----------------------------------------- 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/src/utils/player.js b/src/utils/player.js index 53949989..59372452 100644 --- a/src/utils/player.js +++ b/src/utils/player.js @@ -24,36 +24,8 @@ const player = { set screenshare(value) { if (!PLAYERS[ID.SCREENSHARE]) PLAYERS[ID.SCREENSHARE] = value; - if (!this.external_videos || this.external_videos.length === 0) { - if (this.webcams) { - this.synchronizer = new Synchronizer(this.webcams, this.screenshare); - } - } else { - if (this.webcams) { - if (this.webcams && this.screenshare && this.external_videos) { - this.synchronizer = new Synchronizer(this.webcams, this.screenshare, this.external_videos); - } - } else { - this.synchronizer = new Synchronizer(null, this.screenshare, this.external_videos); - } - } - }, - set external_videos(value) { - //Not really used.. - if (!PLAYERS[ID.EXTERNAL_VIDEOS]) PLAYERS[ID.EXTERNAL_VIDEOS] = value; - - if (!this.webcams || this.webcams.length === 0) { - if (this.screenshare) { - this.synchronizer = new Synchronizer(null, this.screenshare, this.external_videos); - } - } else { - if (this.screenshare) { - if (this.webcams && this.screenshare && this.external_videos) { - this.synchronizer = new Synchronizer(this.webcams, this.screenshare, this.external_videos); - } - } else { - this.synchronizer = new Synchronizer(this.webcams, null, this.external_videos); - } + if (this.webcams) { + this.synchronizer = new Synchronizer(this.webcams, this.screenshare); } }, set synchronizer(value) { @@ -62,18 +34,8 @@ const player = { set webcams(value) { if (!PLAYERS[ID.WEBCAMS]) PLAYERS[ID.WEBCAMS] = value; - if (!this.external_videos || this.external_videos?.length === 0) { - if (this.screenshare) { - this.synchronizer = new Synchronizer(this.webcams, this.screenshare); - } - } else { - if (this.screenshare) { - if (this.webcams && this.screenshare && this.external_videos) { - this.synchronizer = new Synchronizer(this.webcams, this.screenshare, this.external_videos); - } - } else { - this.synchronizer = new Synchronizer(this.webcams, null, this.external_videos); - } + if (this.screenshare) { + this.synchronizer = new Synchronizer(this.webcams, this.screenshare); } }, }; From e503a6de9ca52cc2536399a18829984767f278ef Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:28:24 +0900 Subject: [PATCH 62/91] more simple implementation revert the changes --- src/utils/synchronizer.js | 69 ++++++++++----------------------------- 1 file changed, 17 insertions(+), 52 deletions(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index a80f20fc..8c717af6 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -53,59 +53,26 @@ export default class Synchronizer { this.init(); } -/* - syncVolume() { - const volume = this.primary.volume(); - const muted = this.primary.muted(); - - if (this.externalVideos) { - this.externalVideos.handleVolumeChange(volume,muted); - } - } -*/ - handleUpdateTime() { - const currentTime = this.primary.currentTime(); - - if (this.externalVideos && this.externalVideos.time !== currentTime) { - // only this one works, but volume, muted, rate are not tractable from here by this way.. - this.externalVideos.time = currentTime; - } - } init() { STATUSES.forEach(status => { this.primary.on(status, () => this.status.primary = status); - if (this.secondary) { - this.secondary.on(status, () => this.status.secondary = status); - } + this.secondary.on(status, () => this.status.secondary = status); }); - if (this.secondary) { - this.primary.on('play', () => this.secondary.play()); - this.primary.on('pause', () => this.secondary.pause()); - } + this.primary.on('play', () => this.secondary.play()); + this.primary.on('pause', () => this.secondary.pause()); this.primary.on('seeking', () => { const currentTime = this.primary.currentTime(); - if (this.secondary) { - this.secondary.currentTime(currentTime); - } + this.secondary.currentTime(currentTime); }); this.primary.on('ratechange', () => { const playbackRate = this.primary.playbackRate(); - if (this.secondary) { - this.secondary.playbackRate(playbackRate); - } + this.secondary.playbackRate(playbackRate); }); - //do this at external_video-player/index.js - // (not by passing props but by getting the values from primary player directly (dirty..) - //this.primary.on('volumechange', () => this.syncVolume()); - - // Actually this does not work at all.. - //this.primary.on('timeupdate', () => this.handleUpdateTime()); - this.primary.on('waiting', () => { if (!this.synching && this.status.secondary === 'canplay') { this.synching = true; @@ -120,21 +87,19 @@ export default class Synchronizer { } }); - if (this.secondary) { - this.secondary.on('waiting', () => { - if (!this.synching && this.status.primary === 'canplay') { - this.synching = true; - this.primary.pause(); - } - }); + jjjjthis.secondary.on('waiting', () => { + if (!this.synching && this.status.primary === 'canplay') { + this.synching = true; + this.primary.pause(); + } + }); - this.secondary.on('canplay', () => { - if (this.synching) { - this.synching = false; - this.primary.play(); - } - }); - } + this.secondary.on('canplay', () => { + if (this.synching) { + this.synching = false; + this.primary.play(); + } + }); // IMPORTANT: Blink holds the secondary media down while the document // page is not visible From 791701182a58686781944ee0c1713247868ce1cb Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:29:26 +0900 Subject: [PATCH 63/91] Update synchronizer.js --- src/utils/synchronizer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index 8c717af6..16882e43 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -87,7 +87,7 @@ export default class Synchronizer { } }); - jjjjthis.secondary.on('waiting', () => { + this.secondary.on('waiting', () => { if (!this.synching && this.status.primary === 'canplay') { this.synching = true; this.primary.pause(); From ee9fcb6cf9dcae9f69d16d25bd0bac559b3c2ac9 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:32:26 +0900 Subject: [PATCH 64/91] Update index.js --- src/components/player/content/index.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index 1e15f4d0..765ff24c 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -12,16 +12,6 @@ import layout from 'utils/layout'; import storage from 'utils/data/storage'; import './index.scss'; -import { - //getCurrentContent, - getCurrentDataIndex, - //getCurrentDataInterval, -} from 'utils/data'; -import { useIntl } from 'react-intl'; -import player from 'utils/player'; -import { useCurrentContent } from 'components/utils/hooks'; -import { ID } from 'utils/constants'; - const Content = ({ fullscreen, handleSearch, From ac5b8ceda35918d4766e6d89bce74a9c35c15cf6 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:33:42 +0900 Subject: [PATCH 65/91] Update index.js --- src/components/external-video-player/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index e3d72382..f7c89911 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -9,6 +9,7 @@ import player from 'utils/player'; import './styles.css'; +import { useIntl } from 'react-intl'; const intlMessages = defineMessages({ autoPlayWarning: { From 5bdbf15e317f12abf1ea21405c53703bfe612751 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:34:07 +0900 Subject: [PATCH 66/91] Update index.js --- src/components/external-video-player/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index f7c89911..7b77321c 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -9,8 +9,6 @@ import player from 'utils/player'; import './styles.css'; -import { useIntl } from 'react-intl'; - const intlMessages = defineMessages({ autoPlayWarning: { id: 'player.externalVideo.autoPlayWarning', From 3f4f70ab234912e819c1e4382c66923e0eef0bf6 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:34:45 +0900 Subject: [PATCH 67/91] Update index.js --- src/components/player/content/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index 765ff24c..b6f2d04c 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -12,6 +12,8 @@ import layout from 'utils/layout'; import storage from 'utils/data/storage'; import './index.scss'; +import { useIntl } from 'react-intl'; + const Content = ({ fullscreen, handleSearch, From 0d781b5e3f7a11f9d5f055fc0dec67c5ed2a4fd5 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:35:42 +0900 Subject: [PATCH 68/91] Update index.js --- src/components/player/content/index.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index b6f2d04c..998595da 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -11,8 +11,19 @@ import { isEqual } from 'utils/data/validators'; import layout from 'utils/layout'; import storage from 'utils/data/storage'; import './index.scss'; - +/* +import { + //getCurrentContent, + getCurrentDataIndex, + //getCurrentDataInterval, +} from 'utils/data'; +*/ import { useIntl } from 'react-intl'; +/* +import player from 'utils/player'; +import { useCurrentContent } from 'components/utils/hooks'; +import { ID } from 'utils/constants'; +*/ const Content = ({ fullscreen, From d9d713a2540cc54668bc759295cae7b88d899991 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:39:23 +0900 Subject: [PATCH 69/91] more simple implementation --- src/utils/synchronizer.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index 16882e43..2dc8f5b5 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -40,9 +40,9 @@ export default class Synchronizer { this.primary = primary; this.secondary = secondary; - if (externalVideos) { - this.externalVideos = externalVideos; - } + //if (externalVideos) { + // this.externalVideos = externalVideos; + //} this.status = { primary: 'waiting', @@ -107,17 +107,13 @@ export default class Synchronizer { document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') { const currentTime = this.primary.currentTime(); - if (this.secondary) { - this.secondary.currentTime(currentTime); - } + this.secondary.currentTime(currentTime); } }); EVENTS.forEach(event => { this.primary.on(event, () => logger.debug(`primary ${event} ${this.status.primary}`)); - if (this.secondary) { - this.secondary.on(event, () => logger.debug(`secondary ${event} ${this.status.secondary}`)); - } + this.secondary.on(event, () => logger.debug(`secondary ${event} ${this.status.secondary}`)); }); } } From 63b82afe5b8374b1dfb1815061cf96fcb9554c79 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:43:19 +0900 Subject: [PATCH 70/91] Update synchronizer.js --- src/utils/synchronizer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index 2dc8f5b5..04c185fe 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -36,7 +36,7 @@ const EVENTS = [ ]; export default class Synchronizer { - constructor(primary, secondary, externalVideos = null) { + constructor(primary, secondary/*, externalVideos = null*/) { this.primary = primary; this.secondary = secondary; From e1f98a81794179e1effd926f0bbed1dd8eb6c25c Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:58:05 +0900 Subject: [PATCH 71/91] suppress warning of autoplay block --- src/components/external-video-player/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 7b77321c..706f27cd 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -251,7 +251,7 @@ class ExternalVideoPlayer extends Component { this.lastTime = this.time; this.primaryPlayerPlaying = primaryPlayerPlaying; - if (/*active &&*/ !this.hasPlayedBefore && !this.autoPlayTimeout) { + if (/*active &&*/playing && !this.hasPlayedBefore && !this.autoPlayTimeout) { this.autoPlayTimeout = setTimeout(this.autoPlayBlockDetected, AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS * 1000); } From 4e23e34b59a25f76b4ee31217a525beeeadd9055 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 10:03:45 +0900 Subject: [PATCH 72/91] Update index.js --- src/components/external-video-player/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 706f27cd..9e0c945c 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -235,7 +235,7 @@ class ExternalVideoPlayer extends Component { } orchestrator () { - const { /*events, active, primaryPlaybackRate, primaryPlaybackVolume, primaryPlaybackMuted*/, videos } = this.props; + const { /*events, active, primaryPlaybackRate, primaryPlaybackVolume, primaryPlaybackMuted,*/ videos } = this.props; const { playing, playbackRate } = this.state; this.time = player.primary.currentTime(); From 09e129f1cfd85b1c584f0a44799cc19e69e0aa94 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 10:07:33 +0900 Subject: [PATCH 73/91] Update index.js --- src/components/external-video-player/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 9e0c945c..e6343e72 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -256,7 +256,7 @@ class ExternalVideoPlayer extends Component { } const currentVideo = this.whichVideo(videos, this.time); - const index = getCurrentDataIndex(events, this.time); + const index = getCurrentDataIndex(currentVideo.events, this.time); //if (active) { if (currentVideo.url !== this.state.urlPlayed) { From a53df84003b43ff76a93e022d1b5dc175ef60fb3 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 10:33:14 +0900 Subject: [PATCH 74/91] Remove 0.75, allowing 1s inconsistency because starting the video usually delays ~1 sec due to loading the data. Prevents an initial quick reload happening every time when a video starts. --- src/components/external-video-player/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index e6343e72..fc6e5f29 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -215,7 +215,7 @@ class ExternalVideoPlayer extends Component { } // Seek if viewer has drifted too far away from presenter - if (Math.abs(this.getCurrentTime() - time) > SYNC_INTERVAL_SECOND * 0.75) { + if (Math.abs(this.getCurrentTime() - time) > SYNC_INTERVAL_SECOND) { logger.debug(`Video synchronised! ${(time - this.getCurrentTime()).toFixed(2)} `); player.seekTo(time, true); } From 56fbf1c026f7b276efed1f5ef2461cf7d63ffd91 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Mon, 23 Sep 2024 13:20:19 +0900 Subject: [PATCH 75/91] Add files via upload --- src/styles/assets/icons.woff | Bin 21276 -> 17840 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/styles/assets/icons.woff b/src/styles/assets/icons.woff index b491aaa7b4d334abb0a6243800a2e5337fc33d9d..5ace36bc90afb5f168c7784ee31eba11ee93d276 100644 GIT binary patch literal 17840 zcmZTuV{oQTunji$#