From 96c1f82fb1d77d480ae4e0323945a41da7e5bf29 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Fri, 3 Jun 2022 00:41:51 +0200 Subject: [PATCH] Add play alert feature (#356) * add play alert feature * create constant for fallback duration * improvements and fix buffers * move seek to top * fade in at resume --- music_assistant/controllers/stream.py | 65 ++++--- music_assistant/helpers/audio.py | 48 +++++- music_assistant/helpers/process.py | 2 +- .../helpers/resources/announce.flac | Bin 0 -> 59310 bytes music_assistant/models/player_queue.py | 160 ++++++++++++++++-- music_assistant/models/queue_item.py | 5 +- 6 files changed, 241 insertions(+), 39 deletions(-) create mode 100644 music_assistant/helpers/resources/announce.flac diff --git a/music_assistant/controllers/stream.py b/music_assistant/controllers/stream.py index 246dddb5..12031eca 100644 --- a/music_assistant/controllers/stream.py +++ b/music_assistant/controllers/stream.py @@ -13,6 +13,7 @@ from music_assistant.helpers.audio import ( check_audio_support, create_wave_header, crossfade_pcm_parts, + fadein_pcm_part, get_media_stream, get_preview_stream, get_sox_args_for_pcm_stream, @@ -73,11 +74,16 @@ class StreamController: enc_track_id = urllib.parse.quote(track_id) return f"http://{self._ip}:{self._port}/preview?provider_id={provider.value}&item_id={enc_track_id}" + def get_silence_url(self, duration: int = 600) -> str: + """Return url to silence.""" + return f"http://{self._ip}:{self._port}/silence?duration={duration}" + async def setup(self) -> None: """Async initialize of module.""" app = web.Application() app.router.add_get("/preview", self.serve_preview) + app.router.add_get("/silence", self.serve_silence) app.router.add_get( "/{queue_id}/{player_id}.{format}", self.serve_multi_client_queue_stream, @@ -120,6 +126,19 @@ class StreamController: self.logger.info("Started stream server on port %s", self._port) + @staticmethod + async def serve_silence(request: web.Request): + """Serve silence.""" + resp = web.StreamResponse( + status=200, reason="OK", headers={"Content-Type": "audio/wav"} + ) + await resp.prepare(request) + duration = int(request.query.get("duration", 600)) + await resp.write(create_wave_header(duration=duration)) + for _ in range(0, duration): + await resp.write(b"\0" * 1764000) + return resp + async def serve_preview(self, request: web.Request): """Serve short preview sample.""" provider_id = request.query["provider_id"] @@ -148,9 +167,7 @@ class StreamController: # send stop here to prevent the player from retrying over and over await queue.stop() # send some silence to allow the player to process the stop request - result = create_wave_header(duration=10) - result += b"\0" * 1764000 - return web.Response(status=200, body=result, content_type="audio/wav") + return await self.serve_silence(request) resp = web.StreamResponse( status=200, reason="OK", headers={"Content-Type": f"audio/{fmt}"} @@ -414,6 +431,7 @@ class StreamController: resample: bool = False, ) -> AsyncGenerator[None, bytes]: """Stream the PlayerQueue's tracks as constant feed of PCM raw audio.""" + bytes_written_total = 0 last_fadeout_data = b"" queue_index = None track_count = 0 @@ -429,15 +447,15 @@ class StreamController: # stream queue tracks one by one while True: - start_timestamp = time() # get the (next) track in queue track_count += 1 if track_count == 1: # report start of queue playback so we can calculate current track/duration etc. - queue_index, seek_position = await queue.queue_stream_start() + queue_index, seek_position, fade_in = await queue.queue_stream_start() else: queue_index = await queue.queue_stream_next(queue_index) seek_position = 0 + fade_in = 0 queue_track = queue.get_item(queue_index) if not queue_track: self.logger.debug( @@ -524,19 +542,25 @@ class StreamController: # HANDLE FIRST PART OF TRACK if not chunk and bytes_written == 0 and is_last_chunk: - # stream error: got empy first chunk + # stream error: got empy first chunk ?! self.logger.warning("Stream error on %s", queue_track.uri) - # prevent player queue get stuck by just skipping to the next track - queue_track.duration = 0 - continue - if cur_chunk <= 2 and not last_fadeout_data: + elif cur_chunk == 1 and last_fadeout_data: + prev_chunk = chunk + del chunk + elif cur_chunk == 1 and fade_in: + # fadein first chunk + fadein_first_part = await fadein_pcm_part( + chunk, fade_in, pcm_fmt, sample_rate + ) + yield fadein_first_part + bytes_written += len(fadein_first_part) + del chunk + del fadein_first_part + elif cur_chunk <= 2 and not last_fadeout_data: # no fadeout_part available so just pass it to the output directly yield chunk bytes_written += len(chunk) del chunk - elif cur_chunk == 1 and last_fadeout_data: - prev_chunk = chunk - del chunk # HANDLE CROSSFADE OF PREVIOUS TRACK FADE_OUT AND THIS TRACK FADE_IN elif cur_chunk == 2 and last_fadeout_data: # combine the first 2 chunks and strip off silence @@ -617,15 +641,16 @@ class StreamController: else: prev_chunk = chunk del chunk - # allow clients to only buffer max ~15 seconds ahead - seconds_streamed = bytes_written / sample_size - seconds_needed = int(time() - start_timestamp) + 15 - diff = seconds_streamed - seconds_needed - if diff: + # allow clients to only buffer max ~10 seconds ahead + queue_track.streamdetails.seconds_played = bytes_written / sample_size + seconds_buffered = (bytes_written_total + bytes_written) / sample_size + seconds_needed = queue.player.elapsed_time + 10 + diff = seconds_buffered - seconds_needed + track_time = queue_track.duration or 0 + if track_time > 10 and diff > 1: await asyncio.sleep(diff) # end of the track reached - # set actual duration to the queue for more accurate now playing info - queue_track.streamdetails.seconds_played = bytes_written / sample_size + bytes_written_total += bytes_written self.logger.debug( "Finished Streaming queue track: %s (%s) on queue %s", queue_track.uri, diff --git a/music_assistant/helpers/audio.py b/music_assistant/helpers/audio.py index c17cb86e..d12d5afd 100644 --- a/music_assistant/helpers/audio.py +++ b/music_assistant/helpers/audio.py @@ -93,6 +93,34 @@ async def crossfade_pcm_parts( return crossfade_part +async def fadein_pcm_part( + pcm_audio: bytes, + fade_length: int, + fmt: ContentType, + sample_rate: int, +) -> bytes: + """Fadein chunk of pcm/raw audio using ffmpeg.""" + # input args + args = ["ffmpeg", "-hide_banner", "-loglevel", "error"] + args += [ + "-f", + fmt.value, + "-ac", + "2", + "-ar", + str(sample_rate), + "-i", + "-", + ] + # filter args + args += ["-af", f"afade=type=in:start_time=0:duration={fade_length}"] + # output args + args += ["-f", fmt.value, "-"] + async with AsyncProcess(args, True) as proc: + result_audio, _ = await proc.communicate(pcm_audio) + return result_audio + + async def strip_silence( audio_data: bytes, fmt: ContentType, sample_rate: int, reverse=False ) -> bytes: @@ -372,6 +400,8 @@ async def get_sox_args( "-i", stream_path, ] + if seek_position: + input_args += ["-ss", str(seek_position)] # collect output args if output_format.is_pcm(): output_args = [ @@ -389,8 +419,6 @@ async def get_sox_args( filter_args += ["-filter:a", f"volume={streamdetails.gain_correct}dB"] if resample or input_format.is_pcm(): filter_args += ["-ar", str(resample)] - if seek_position: - filter_args += ["-ss", str(seek_position)] return input_args + filter_args + output_args # Prefer SoX for all other (=highest quality) @@ -442,6 +470,17 @@ async def get_media_stream( ) -> AsyncGenerator[Tuple[bool, bytes], None]: """Get the audio stream for the given streamdetails.""" + if chunk_size is None: + if streamdetails.content_type in ( + ContentType.AAC, + ContentType.M4A, + ContentType.MP3, + ContentType.OGG, + ): + chunk_size = 32000 + else: + chunk_size = 256000 + mass.signal_event( MassEvent( EventType.STREAM_STARTED, @@ -486,7 +525,10 @@ async def get_media_stream( streamdetails.item_id, streamdetails.provider ) # send analyze job to background worker - if streamdetails.loudness is None and streamdetails.provider != "url": + if ( + streamdetails.loudness is None + and streamdetails.provider != ProviderType.URL + ): uri = f"{streamdetails.provider.value}://{streamdetails.media_type.value}/{streamdetails.item_id}" mass.add_job( analyze_audio(mass, streamdetails), f"Analyze audio for {uri}" diff --git a/music_assistant/helpers/process.py b/music_assistant/helpers/process.py index c790b4ca..958e64b1 100644 --- a/music_assistant/helpers/process.py +++ b/music_assistant/helpers/process.py @@ -14,7 +14,7 @@ from async_timeout import timeout as _timeout LOGGER = logging.getLogger("AsyncProcess") -DEFAULT_CHUNKSIZE = 176400 # 1 second of pcm 44100/16 +DEFAULT_CHUNKSIZE = 512000 DEFAULT_TIMEOUT = 120 diff --git a/music_assistant/helpers/resources/announce.flac b/music_assistant/helpers/resources/announce.flac new file mode 100644 index 0000000000000000000000000000000000000000..95c7caec38691d579f02178bd6cf35b4f23525cf GIT binary patch literal 59310 zcmV)GK)%0bOkqO+001Ho01yBG0dW8!#0A^}@Bje8cqD+1`i}{z(IBM;$GS_4;R65w z5&!@I00000000000000001yC#002ZF0001TWoBh^Wo~0-AZ%%3Mod9NATcg8E;Ar9 zFflnWI50E;0000O0000(Z*6U5Zgf3Ra&Kd0b8}^6AYyqSQ*T)R_{SIk?ot4LmYguiVTNR}nS>xo zKjfJ+gof&qGUo{<7nCFqX%`G3A;~%UB%vX~OUW`r zCSeZ_%8Zo=K;a3JQgk;WPJ|>RA)NFrOe7#kWS&Fky=F)fNe_|bgqi?mftw*@Cz-?N z$uNhO%rXX2WFSc;QMM8ggpyWyKh8-z*$80*Lm*e+Lku>n$UuPzNx98ukmM&0xTy$& zNpqFSY@(2>i{dE}ZS$fj5SIwHh-*bozC=Vsi-?4xuT3jyQ3ynmR{xO^2uk9nMOccm zy(&uieo9CoArVb4$;gyMMYfY|b6s}#`8sc=%1Y?XSFl1LnJb1VQ02seVPXiLVhn@}gc$X?Pshz=*8UIffF@6*!j7-SH=e@E8w3 zT_CW~D?`FAOy|*{1f3SB)`RRc$#um3Z0T@iEZ)#AsA!no&y-u!o4p{Nz>KR}c6i>a zjZRL7r+g=~kR-Gn9^otjR{E(qAU~|!tyS$X%%a#ijOI^yldTOs&CjJH{hc)*x!3#4 zOIuFD*JX?M?MaX*f)3=!%}}TknZmQ>!-2QP2#>?3ZNNs`xXf2FZ(l9|q&Lypr$YFJ z(A#3$!Ge*%Jf$vga?v_WhYAsN3fZknRcH!2mq#=qbvA4pTjG z#NxA}`1ZtF(Z~`-PVlzSW8NjAU)*Fsd2-cF4Ud$Q*NZam)^gMn5k z^PKlDPzzE{q?!znohPlqOn!s{4FrxQiIH^WMd*}gO0dEaJW)w7R)PsaA{{N|Wr=t0 z#!XZcaYgSRJ@odSd2CME*a&TE6l6FTEw7t2_d;80!TRMjM?%VE%U zwxYf-Op>790@_N?fWwve&?X(q+ZJ*(36-^4k^C9)^Js_luzndfkezKi#7P5JIQ3c$|!i& z?uliI74UM2tfh!3mJ+wgp=X*t9P^5o6r-mAxqKj7h%rx@RyL%BmDcIiKXwnQOO|@X zcD+Tj|J=%EL4agCr!^DLRl~mILn{Dn1rTN zH($?f=)Ykn{{)NdGbD;cq~gV7hT=&tJ+WJ;sE%R}*wvh&P5GOt#~T=*k3(ZdVZ64v z$jHVQkCy^mpy5F!Qqa9v@UWiO>{nE!N}i@gmk}+3X>P2?G7Z5=T^pkzV%X8q%L(9+ zZXuI6h1n_U!6x3Ii-ww918wrzO9D&MI*Xu0*3vH+j&T=63G%#tmjb+94Nj zl#RI!`b3{|9as6JZ}A@j^^)NcIG~i>f=1Md)kRBQ+X zB020QKkf)99GUptE)er(25^^^t1ly!GowG~9%{;d7N$KrfM8m8!rbs><}!SayhIdC zVwj7&V!%soIFVXa#Z7B4xBeRp4^&JU#Z!d?jE z6f$)LL?p+7++GqDEhmrK&XtJ#Gg)0I6yEX7DYcmv+hX}ihDA@_Xt2IJE~YFmGEwyX z6!fKZK)V>W_|USVhvF|fa!aE*-=CCLWUdkV(2Dgg=YsIycqu~gzW&!CVl96WRpybH zL^BN;Njc5zcCh;MjY6(9#qC@+i45HmxP%F9rYZy?d|Hn>^riS^E~bPx;`=qDe7FYf z@(a@OLAu<27#Z;IbaJ_qbvTSKn#qLjKuz^|ZZT&fLh8FHC}wRoYl8A7Gkj_wFGi@uS;31QH5K{DYm=VtW<+8q-PMQWu6K+RBpHkZ##K=E)B_xNqpZlA{Y4Q4H1q zu9I$&FPAU1w-kExG83E#gHF)`Bje$U<;i6-Lu_*o-0dpG(l-azQC*!)kwhl$QCCSw zg=GV2k`xjlc*M`@yzg{wpvC%h#YLproSGr^;*q_Jkcxnva+R0GBqFA^3o$riFgd`! z!lwnW3-)>^w(_!9ohceav1b^qBeL9xPls*v1dEoiBO|Zf~YIzjdHMMsAQOOA?xL2$`@;c;mrKoOCUid0R2rd{t*E zX%$VpvBDP>Sj#d|y&AK^=81BeS&~3D=leV;Q-1xpr=ckuB zvr2o?lL%y#8B1ByS8aHy!Esu=n5)JNXtD}Yl*-6Nsbb9imlfn>67s-zQb@b}_Jg$8 za&Km4nM%pU;!6^fZ{FsQ(I$;OFa0AYPY`QlV_M%Sad(?)#Z0RWGT`hbXDKvAl%mBk z%GP)1_nbtC=0B)1TIw5-GKqhEBm`u{%Q2H?XiWXCf{Op{jEQcB(#N?L_he%uSx?Iy$##zd-d-Dx674pnmZ+S)vyd%% zB9R+~yR_M|<=&cBKAT@FV7!-{;x{ZNoUPJ3`EtHeEU1&5dd(jh1v`a%WbUz7)09_2 zq~DR0>nW%qvkQhg@KB+(xpiq9$*NC<3i@{|qF!A@A}1D9a7bkOgCDN6K8Zvw3v{aa zn$hu39R~`g90eotX_X0b`0msbBU>q_=dq~bPIOl9erX8>mrA+%<^Q~72%=3hWj@lm zw$)V<0V2Or5lLYWPY%?D2sc7`T{>&=Vq0$f@`?s<$->03Fvp%h6K!heOlU%a|~8pTfoJc_I; zORU=QPO}NFFH%L65(fFkj1o;}23~84H8vfm12TEye1bbqg^Kwa4V!6y0eCiKlh-+e zb&RH`QX8WiNUi5ti-Q$1i0(d%`5wkXOm39N0xajdY9JnTX(n{Ifisl=0000006+i$ z000000000$06+i$0Bw4wrr(9)k}|MJx_Ksp>k5i6K*>niNKQ&ZOeMT2B#w$qgd_bk^u?He1xBjlLsq@`&Y zgoHEXl1<8zLP8QrDd-YtS%yP7@5vz{B(|~=NCYJ2;gUj;(+F5}fs$;qG8jp(NJS(| zBvv9y5@fz!OG!4aNf9k1q>}%&V-lhgAr-IO`Kc08NhDI@MG+UMks?bp;Yd;?J}V+r zNr`J0mQ0f-s96X}5U0~3B?~WB$deP<@$yvU)hb$wOz8}wjdwT^$`GKAP$nfh0AhlG zU^od7xhdmXI0N#DgU~GT;>O#6Tq&1sYB^L|`PAvVWpQ<1YbgVWmzFEN z!U^vmYFo>ukTr6KG85ePGCoAN4YW|eiONzd=&6v3slrZ{dZkSzDLxWJ_|V68WRo@9SM2t_NCOy-q1-d>wTlVgjF(*FYX1A6Ayd$QUQ&8l6}fkvlRwkSo2 zn2hFoQ5G-7^uN*71c}6eW1~n3K+`v5mxt{<{5>T*SiD%oCa0b#aP%>s#_ay(z4@0` z1mcN8nw<_>jnz1-A>)Pw<`&#k4U=A z$4;A+Fm@+B9>`y`tb9thnir`JfA}d49||rcpFDD6946Oxfs{+Xbtx(cpw6(w+I!6Z zN-V6#na_?@`7;J*zdwyRTzDGd3qsUnk&d-%0X)n(QtRl|q7}8G*fp`O%MyH-q{b7Q z+BmE!-N3Ute@v9pY)QJVJqj$&)7geD2z;H1PiW9817|kYt9?tl@@e~@lPZ;7vNBX! zO)^go7ehW$orq#5(5m88tca4|IF9<02~(N-TG>f=izsCmrEyS5tKx`2ubK_e1)bZ^ zj1ROp4q~PGS!_rqj42r-JO0fAZjT}rk{V)XS2Y?X^%aw*yHJ*m9Sm#)F--#Z4Q}r~ z+Qsd2-WQ~-uB!GH8F+0+3whB^WV4}vi_To*4e(Q5Is<<$Y;qv87Yt&`nK)r4h@-_U zk(+32sLONE*;J(PGm*$0TLK`kI=&$dr>q=OwW_ByEOSc$nbw9w%TSt~b)PmlDnyYR zH6OQxOE-H<4oB{mHSpkk962Y!-3hqDhtzTXHCLLg)yuV;@=17_tfQI6EcNTT`y)Lv zMpIO(JS_Fm;=4WBOqS_ ze>N>dd=^=kQua|;g?+Y-hjBV78nEti6Ie{Qso)!U+c;)7T5$F-t5;Sf=3 zbIqu4^e?YBluyUd#qzZ0;4nI8v|=ISWWYsy&O+7<~&z>F;j zN@QXd5768vy9y_AlF460TWQ3>wHQLT@AG-3@_3prj7@j+Ya>N+u4-6k1y6+Hae$dU zI48{Fu6ro3rY-N<+9TAavewkdxHcIR*@dA}H*b;|82dtUdlKQiU1upomR;2E(V#7` zFW5Pzu`sU@ZdxI7Fd=*pkVc7P0~IGN97|j-u9M;Yd0k(5CnAq6cM6@)5-NlwDIW{d z)4f+oKN}3F(dBP}j8|f0DUxC8Z15?W=_m%;Ct{9|Waf%MEi{_DCm}mDPhO!q3k3Fe ziND14{<6Ce&vqRzA<G&B(LNMy9Bc8k#sUEHpDID(0@-!rv~8ci8GFt^QKqu5KwC zKPVz-j0u_ve%Fhb%8BE$v58#5j5DWRZG<8O0FG@D7);|DdPuRV#3c))Ut9b;1Hz@P z0NJhen+(>iGIwMpu;UoBO^pv!n5YPIK39<>tt(j=QnwQ4B>ZKBR6%L})M@_h^b33l z)W~i-xp6p@S5mAKW!^Dvf}^+q849!XHX;u)C!^vaT|{NNl&2a0Ch)Z-RvHyXL|wn( zfz)O7A)+ASDMg-vy$|l*ZiTVAzki?8ZilR(G>Q&@FGaSI0VWOg~~xaryUFU zQpwOa4rzod-)KaGXXAF+F@jML;9QwtA}BLj^1{dopdeKMR(tTU-KQ;7mj#r{d3;Mjghf6QhTyMPgQBU_d8YKM}BP z-IiXd3NaWKq%=X5VLOtMn4a#58wwF4(M zFBRG?aGMPcPty_qGhS70Yc-?Sp$4~PNzwm)u^H8Xfx9rVI-jIg^i4`Px$~Bh<}PKG ze%|3Kq@xwV`TCai)2oI=U` z6>sW~x(l2ma)Fxsg^b58u}T{Whp>r^#UW%%u+wG-8qkSiF^!X{NuTPMA#|kob{)JG zL@mh8%}TzpWsm$7eiI23ot9jLRM2LhwDYJqT1I+`YmpmPyk?L*{bOL*BX&rbkr2gI zbDSDV?8-q-M$91#{EB~ZEaj?tjkgml7v~_`(M7Z$3IB8*C?hj1Y*6-RlrJ2vPq4_Q z@p*#Clm@;^iHl&!&uJQ(SosolDM5u)3J7UNu2d{BsJtaGCLm5yAcOQnrN+H8nYS>D zDp9JSiNrSYt|k*z6GviNarl5{5tUq1LA3CuA@C%R*N+)QUO?)j5upht}aR>uGvN(Kkx4nj|If zg+;#V#gr;Az_z0#sh=Y6=JtdX)2Vu;i25z`zdT!PX7TVx1BfO-LUJN$>&ZD?*u13B zO~vD@>Xe(Y z!n=H`m#Rv@!;(z;6zXo_yozqTuDui8GIyHzoTQWM1F!+mVsUr{Mgz5EkwJ3djR7$! zIToeb$`07%TsG%$m3lR`UJ;ebLSlLk;L0PD)MjJ&%S;*00xr9;_B7V=eJ2 z5EY5ysb%NF9QaVFQ1>fgIi976F^AmIB$0jgcN3z4b5gwAqa_tpqzZ|exo#}cUKENK zZE1M1UtXD#DCtcC=BKg`*_on3c$!{bE}mGx3xElRXTi7tyui|ch)!SDiTaa0a_3JxEN^9EXX5@iF~REdT| zTw+LDnLdo7R_aNKPUX78H$5Nn)?}6I|Fco(nR&-`6qfASxi^%|7d=^^xnI7@L-F4L z!CxyFIPT^zbqFU1Gc(i8W|ITaS0YbHMffyDhlnsU>bbnL@PuJsnjqnMEd{u#~1T$^Q!+CinLbtc}?jPKV%VTOQRyQPsc zSAitAaoQ6%8RB7h%vUt=Q)wwzGS{?#fmacvMWL&>l@OH?iTgFuPSg0ZQEm%i;G3G5_+iABU0=_rj8b=+GO zIuBZ>jUh55(v5^>O7;PX?N6HPb$}};3S9hT1JFnl)H=4t{!77WJRKcuQ@53{*amT`I0k7qv$K3 zDnJ4o08p@jG|&h$AOPDWOii%jrm8fs3z&Za`G2q z8xJ7p?=g)WgAAljseb{5lX2S<%HAS4k1!_G;MDTe6|0u=l=ptSbef4?6CCmqJyirV zM7uf^$@vh7(A;Fs$vVhNIAwAlLnw;d(cJjRLnoJ=(PKpOsEZ@7_|=7l@~*T`dbLtM z;rT&_e(xqQ;tNwx+ED`GP?a_KlRoy7X`;3iv2f&w$;!Leo|z_+f-JZOBBap~0P<4J ztru=q0*&117hV6&hnYqVt#B7MwJYNG?3avK&EIU*m|sUA*a^KEWfjE;h7P3y4LJi6 zdr=o;l9A@IMo>W}w^BY{3}n`Rht*9MUJ@w@aDw~6X;3DYT9w8_k4cQJL9eJ1Jb2cRo>%8&d;g@MqS7Wl(iIH~czZj%PK4ztIk9n01>{Wasmn0AgFTP#h z8HdckP8PvoFg;*4NOO?xLBn(-?=R+2J|~I;JCf*-d^HvjNDu5pq2Qt#l6v2tt{bWw z8o|3-Kr(k51{UY`t5}FAhV~C_7lsjFrlnMNSY9t7_XDH3GOoy1%RNQZgCto=m5>0bZll#QRzbkhyeN2bHz`ZW{ePSzjx@EslCc#Q#i!cN3Chc+?DHJ7 zU|^{0Hzzcj3Z8d2RV^8$J5xvdslA)BQ3drz0)=vANFQ%-G&*1@7ZAq61uD5w-&vD( zcwG|xeSm2BBAoq=VDQ!P5tOAfCaX2$2x^ChjFFs)Fm6?5yz=paM61qo(MZIuN2#BxEefnyyVui=7P^c^GV& z(4z+RJ9&D+>RQZ14b6ho)S*5&5D29Z5(^L$g$*y>*a{gqnw<@OwQL7l2hs^U7)CFYpTY?z+h1f zdio6ui=>qOmDvm$ox46OeL4}UksN^`V9TUyWqi4i$%BSq_(}4+O>C|B;GsJ86$)9x zxU{lr!-k0baw9*iNZdGJeJx_C@ECS@ad9wQA{3KU_O}P|Js19Th={3TE|Po`atk*u zEHH;v84qnTHK@TogmsaO2Z`W;`NI>Z&Sn(;gl+2QNs^44G6qOQJ~-fTF0_<{H9W)O zyU*)3gjM*YNB6*yj!#%1Wz})hk_B9N^odMRd&44;7P@N4p0Z?0>|mI?f2~L@(s8u% zKyoTCApc;e;pp*R*A$zrikNrVF}L(&juv%0|3)uhWSwk-v*KYYR}c5&Bq9-!zM`o& zz8SiG)2#FJq6I%LJJiw&3)-SD^zL1 z%%VQtI4&J2bE&JfLMJg%Y(xoOR~U}skB*24is;F)kTOqfB=d`lCB6gJ0pdgEFF?mz zJQM~^OL9(`L0}-GNV!f8A0K?!jEr#!DHJ38-3m4gqsa}Z_34obiBx@z2ZA}4Ax0fo z;PT|;MpdTPtEiN@TDL2DcF2%4bqB&EL|>>&(nH2Y9z**k(zw2q={FD=RXb>KK+%hr z?aL=!3Z#G9L{!sPOzTadl?vGP!)^*58j*=_*oeZFpYqwG&xy*Lz9JJqm5IIu3bAk< zq3!nrXV)(N6-C-^$Gr+7Dqn^*a5h%kMSH>lmjkU-ubT3UVvUNF1)bQX8dFIOlJdqV zcs5y}WBK%qa9P-wCx*3Tp2=|_xL=a-rgD!G+2u)7v1xm3W(eEtGk(P(6|%YD&* zR#NpZu$V03;yvlfb0Pv+yX=V+Vo4FaP4r2yUVuorRKb(JN(ty>_M_#N`PYfd(>$O) zE~#46V|2peR}#RwV!>^=nzR@fD)ISiUWhGu(-H!ct|wG>*=k@(j@!A~(T439Ns@PzIET+XW2C}-x;(BB!j#~LtS3`|7Q5AOu zK017zTI6&Hm}{C$VK|>CFOP@XNo7cRRH=F`J1}*_oTd{x>txP@=kt=`dcZ%9d2Zr|?jmJG2xP{)pJm zBxFTZ(V$c!l+c{sVf^!HCic)(tV_5gkK+=iA3TibhpU-FT1zrq8i})wL?)IcKA}ib z%QTXz(knP(s|E!)nL!xdESMDKvttnx44{0A7UUBx)yBbf{L$sT9^>`LLQy1vxoYL; zBOHc9)m#jg?w79xV_L(Yi9#o3qUBLT4G%zk)kpPHUX;uPqRybvISfv`kv7GZ1#~*i z?rnsQ%-`7@EGfa}Rao3Z_(JnLLRCI^#(XP>fJ@qEGzm*(sutMz`ByM04joRS;8S45Vht6yP@}$3!3m$B9*g{xv?%vk zc4ziOj5Du0eLg{PbVDqU4jhazjOkx2L;-zQRS7b8I66?l^{bv~Tl+aEns?_mBaop- zjoCL8fGH6I{L1gArZb3@#`uKIJdxh)K+aVYwZ~Mod-pvBrJCHFz9032 zT|{C+WWDxMK03VLk40dq2K6;<bl9%>8lH;GHx8D=-j{F2oD_=LkQDW)RHQv6yVPyM_PcTiMU6jB5zU>Q2>C+} zMeYLPh4XNgmkhHOcjFTk?ciu{o*F9zK4sh8HfO?=_AtGMSsxQKQ6*|_VDU9#XSXf_ zPVccy_KgfR3#x&j(iYzGS)OUpL{8$&#hH0YV$Q$$@<^J+sYg0!$vP#_ewmM}V=RH1 z8Ge<2#xBviJ&e5aOlK;ak4!Q_9MVg`Jzj$bK#LHH_Aq3Qb%W_RfIK};OXE?VFQ@H} z-RHb&?MhyhEkd07e@C;GNf=r~oeWoklHt;;O;WWEU2|2W^Ncyrza<0!^eH>ElQ5Cv z$!XNF;4Vc4Lp0R>b~#N|6>!)n*9@`e z(wvu|Tt77gSzw;C^3t!2Kr!ysGv?vOO$(fG$TnvumtrZF~aJ>eA*$If^g)1;%#Tv z$VK3!APw=$q&p<0bjHyB(v(it9J@zl*|4oiVmy_5q$URAOlR_JqbH~nkef$@#G)s3 zx4%vDwdQGpDZHSzwk8M5MEm3hf^)$TEzz18j8X>iwz#ISc=(TXw1xO!$DzbcO#DV9 zW!l=~qS&xgK@@sDAP|`?soMNjP_|F(cWZo#jcZ;{rJf0hjZV1{%ASED@~gk9-kOb# zqFgl61xM$&55HCiq_m1URnV8U9U54|A3{o`cT0EC^l~LDSXTbf45&KN6RQAsSUrFm!HMdG4BuKLwWVW^=2vMk? zH9y5i#Ff%y+hsvA2sB{>&PY#{jF{p;pUksHC>S{UaS=b$?gWi2GIj7zkk)$k$#ZjE zROeKp6=F+^`I4uvVeWhr-i~h?zOeWH$Ec#3jz=L#JACePy(8+HLCR~uxGXb`n7>3?xHT@Klqt3H+U7#EwcnTOsZ_hVxQnA&eV5DZ z-5VOo}=`$Dy- z4yy^>bJ|u!lOK6JFLgAHmx!FdL0WpZ5eE}ry2r15;R*6P-j@^tOdtliQFtmW8{mR5 zL@`1ZQ4oaS>jXic|9Xj2I|6Bph6y%=qHg2-P~#K=1Yd7PXdgCieuH^GDM zmh_^_`G58n!pWsYK+!KR1H#I~$$*H^BA3!*II$MYahu14gEYUEf`c=I+ODipsVqNQ zixTzXulUoAk2L}GHz-lsfM-55x{MbTK*C0l==F}OA`;{Fo;JVZGIkx5jh;*RLo6nq zYS}LOok>gsM%}WBMu^pljEAN4s=5n|e)^a@V=S)Bi*u<+g?CfQTC<{C7X7a4f!brL2z`K^uqg_`C_Y*iGCI)# zsJAjhOXyY?Q}BBdP+)3RFIquJq;Vm`Ho#J%jI?zM0u+XJ<;YC2pP7DS7Zyy{X~uI? z-pxK7_ED8vs+{6f=YYn!6&gyo089W}02l)m;d;XHAZfU|CJ`s=EXbj~n1s(T!zW2Y zowBJ7x~X%kvA1U==Mytr*Ne!<${anpSjdP;wtHrtipIEAP*~k+Oh^_y*St>km|g7~ zLVlub#cHU#AEPceoH0- z5`Ci7RdSBw84)>sIhJ2kqCMsKS35a2f6=c+SEE5lVo}kRi@daTdT5+g)GU;*{%^3c zN^R^Oyd5BVn39^JxMdJ1dbdr5ZL-1*n&_k^Md-OqEdMx_(8W6nJ!w*SQ>8%BT!D|T zI(RG+Avob*P0*ClLP?dz*#fcao-WSi5Kjdl695T-2EaT3P6YtKnEN;}uk2hC8=4?b zF4iY2D?7BN%*{F5M2Tpr=>%j>xj674aOXFK3AP|~WK$uCBm`uHEA1#`hTEoZENoHR zPz6O^x!H4+6xrK+8-7$;{`X2mVl{+cM*K&(qtxe|5sEgYNb8cG&I<0E7G5glpwqR8XnO#HpuPLw=<#6P6SGE)AD5 zn>x?>JRKsZRcM50#xn|6=GdxpQOSDm6oFvq;e4E^Vop7_q9SOnp3s#Xqfdwy2|Djo zimx{}kjOHlacu>6Oh~f)(F8-n06oE7n z8=yrxVuT7_(aym(HOMyRM%IW|IbUf6>&%DZ^kFkR!1uhgj&dNL3$;R$}2$C zi8`%7c5dQPqAUt?@`8%G+XeZM;qe5D1Z~quwrU&6lLw{`^?pbXG{F2TC@zGAL<8>j?IT*_8h4s90uDh2BaqZrJcOT&S52b4GVfeU)x5FaA%?jMt-v4aBcQc&?K zsaM2PO%FbDk(MS1E@2XwJ~%*;rk$C%qt8c0s3`ta5(0@V`%Nsm3k0#lwLl{KBG{Xu zl>!Fs^g8gUWbjzx7uXXL2prc8c~!^DvdRVls0xO~!8M&{1(@=V)=DbYS|FsTD@dZ& zRgg+Usf>li8$$y`SDJ4oLAlP}D1Vw{5HD*lRGQUvCmB`IV%bqFS+NSWRR7}S7c)#W z3LI4TUUwzAM7$E{ByIYXzd^!`Swx35agr`uvbQxlq?>|OWrW)>Mni;lu3Xu^sbFwO zjf}MRxJi-Gky##1T?!-#<500&P9Yi9VN#<&l9fM#m{G?3pbQq2J0mIApua`Uhh8)Z zQ9vni`$mX3NbL5~njs?l}w5Kx$3VDMEB1Bgi!5X%?R?op(;Q#5 zfpG$4scs@zX&30;&lzS4(3-K60}8%s<^|2Fz>Uu8sbS*dkql@L4l4INrd&kYVmI?p z`K07<>izLno67eSf()eZ^=*tDV;XKr87he-_J=#oF1viBQ*XUt7k9T!vVk~;p_oQSD&0R=AHzR6*DP2lT=h-VkXo3~* zKV?H83h}34KoU^`yVs!V< z!Tv?l=FJCg?T%-N6_=e*))3w+uc1Gh2d4G~I>HKyrF!Hauu>&@7vbSv%nibnYWp_Z zB%JDz><}0LZj$Aub5SYKQNRm#K2EyGPst29iw{dI&_X8|y2&>e3J@($IipLD9eI$8 z1Aq1ktgXd)z1Ckk3;WW$3Q3XQKYmsPeSS#qtrM2=;=bz^)4h=Yn+`~dx(%M7*)`Iw zWW9DsJ$5ZI&TKl)UdTQdn(OW3m!bOhb z#(*V>Fr6t>m6Pbl#u_qxB4MbJn?xEgQrB9E2|X!5X(tY0JT=X%&ZLM^aeA=~J6MrZ zi0J_XFm4>6z49MNg7EKN6DY$o8QL7qS8<4}HE0<(P{jtCwA;DNHybxhVQ;z8%xECc zXB6tPAj!HSVoK6@UV@c?&C!$}D(<`{L=iYS$#S8cH|_aD5NtP#%ds5~}V_9J|G1%S$?V~jiB(wn*h+e&L1(3sufQMgJ- zMY4^a@h#P)0w^aOPKcp923hzbvXs?URp08OWyc+|d8gk`iiogwoU zk-I-@o!suv#e-M%LXe0oiEku+IyD9Tk&0*Ml_+dk5B)3cbuLj9VO}H1TIDAfNB{9Q z`2-!8?5_XX;mx$W@+FC@)hnFj6b?g9GGoz}mrn8gHr#7*WZtrC$n6Eu$#!beH5Aa@ z>J(0MWirJhp(A^0&?EzaDPerm7DEIEt$PeDiwkqEI5BATgDe@CsI6*D9*K-OxFmWc3e>HZcBg2I*Dk@>qz=a^C8N% zEE)g!#~1?OR{Mqf*ZP|FzzCKQ#{91QJ_qvdobsd;*|h{;oPN((;bJd|$V7!#yqzz= zh|4n3YgJE$y8XPxM)uc%T6mLKbcW{QP%1Z!C}OdHpN6Uv=vj77QK37Y_+7Nq;MQ$! zsUc+&s-hsT_+rRiY*NbtvP$zY48qW+rkV9MsX#%V=#+xJX=-lWmS#thX9nB;5wP2t zQVK>g6q+cK6}O>?XNRMC-5F{g@98)hwReu;8fL07(Yf(bW(Yb<%5S@Nbw--AL8ICY z>Qbj7`{<;fWWf>Enc62jYF;aS)>x@#npK?d*;4h?>E(Nz1WOw^!%#|;rixsHR;kBrXvwoINCuGFGL@%DRWkoT@qAueCBJp zIOF&6L+t4ort$?I$NRf%tRdOvqUFe4ib3Fi%+jL{zi=IWW&)N#@f z`I((A5uA^&CuE@B5dr&dh4{vI*9jXXRe5zNL?JTj?O8 zZ0$l*dfgkUC2WbV2PXU88S7Hnk2kJ&WG6g^g=sFPl{2i(qRxj>#uc47I9@&;d2)%( zJoH!eD276hQ-UZsAlW-eF(+r?XWhA7XW^DJP|@s-ed`eZhB8 zL*jh5+8Z<#-N!f}DNvcLk9{I~x6eqZh}0x+6LB#ZcHg;FV7Ib-QM!fhm9Oc}u^+vC zP>Y3E)r(W6MYQspbnkolr5z%~hIL{xa1e@Q)G?uj3b$V>om9y-KQG$Ru(97f;jRkoac%LeG|b?@cWLo^A7 zL>!$vf(ba$M?JrNgDEAqc~a~3SPGX;MHbIMx;CqoWVczOpouPLJtwd#Yzh@7bBz2- z>YoehEm>~_M6M5wGgrv5-=eMk@H)t+oG1-05gx|CIm5uW^ZRe;zzVJ6hg) za~UsaezRGNj~_E1X4H-i%+YO9%6@@c#^J5(-)r8W$zVfleirD$I7 z=&b-6=^w{L5LO2?fh1|nmCLC&PKZ0>m%n)p)*0X&pQMId_MFn!U7WY-qjLTsm=+cO zsJNGSMbd}*=O{!d3))MQ@tq6qQ*MDhv_Y+L#G)w&2aCP_G_EZSKT^#sBME8H~Y zx{~);k`K4crOkyfO%KslN|@f`2@-z~MKi+t8iA`IP@Wf8k!xIg4}mQzZ3u!SAWt8+Ucr~M<^UHpR3zT6qd0<%IKzpPHU1X z_HX?_v%OHK61l~cHe7N`dv6L9TF{|Zu;}ur;L&UmOO7!BnFY`)DF&r+oBY-LJy49` zsBNC#(>y^1u+f)Ty#Bq+7TYRPVBazq)|Ynqle@_3{2dSduVwQxGsjf{GIfVJ0%|tW zvQZy3c`HW1SNH;iS5p-q8!#{5Rd&yg$L94kUil(qjmyTgX&gr`UmiGTmHX=29TRr8 zze1H`4DSYW7g*iNF2fIV{Mw<7VH{>@qT5ixUDefa4P zjWvX)#dClHd^tIJXitS~r9IC#s@ZzYgjIRKW8f}Q>h!J5x?lQz6~1j|O-dl_d7#8u zOp7&73Zw1mdFdErVs-E5=k*yUA%1Gk#ZLVS<8l? zhF$*sHl*dbIy+185oc(T?C)A~XR=ipy>G%tuq`aPfAuo(=94=ouRhlqNmHq8dz&APj4M!HwVdyeOX#mw5=bXAZ^kYne(&Epu}2~o>O z*%JE3oKFXt`L=ytj@a&}=F!$cnH@stK11TO@E{p|!m`1OYbCw3477o?=C8dE+v?L@ zXSfGuRzVKt5@MfbCV-0iFkS;Gc&dclgO@TgNA@3M7gU0#D`tZrl-&zGx@d3h`6*9dM3FbEXd)~lI_H<-$n#5WOWNzoT1J&Sy&^J zej=-tIemJBM@Z!HOgwGMf=RVs*Z)HHpxq(7>8c$YCO+j7e9x~klY_~#U6e&=cG-44 zH_K90Ov67-jDv(@Yue57K)rp?jkM%-A4VZawPBF=qvgb4pbkf-1};bw7Y_2z^N7igN87VybNz8uXd$jPC%}>(b zV^qaO&W+j^O%!LkoHG&A#IT4rTc`_9K2Q243Ze}Z*W(={o8|iJ<3y@~*xR%PR`KXF zL!7Co2XhL;}%Ir@xj`$Si&~MAwMTQrR}rrE5(n==3s)AT-%;9zG%@$ zkK6xZn%JH>&v=cBS899Duy~a`quETaJH=n=)4J!!Y?L?n{ejqW`?#{d!1u4~*Nq*l zG!dt8z{udYw9W7rRB)6Ff482=sL_KEJ9>Icp_e zUKP{@1YS7RNK_p>IyU2&cou8(eLzrG%*(j>ew>_8EZ z46e7}r+Jp3lbDw|SDtd4gZVdyhEivhd&D~JNOBU<_#6!l!sG0RIYDr(u z?HY4j%18q7rDLvYln44O^oCqrEFW-aY*a~9%OY(k`P`W^I=W{Qd*cPKN8yJMh5XKv zB@t`ow~V)?E$H>1Gr`E^S81TcB!J&yiutGP7v+25hrfv;%U@jF{E?Kb_cP=5s1o>1 z>l=%0^y@aV(Ec}<#QoKuEGv#SO-eiRTLpep`KbR;H2iD0fK_f;ub|T-)nwkFsfuN~ zOHZ8PdzJN-$ZU?BMDpuLA5969S#{G-~w7Qc0o=PKC9@^?93^ZWws z&FjJd_|5;E?Tq(r-_n@ zSDuN)AlS~F{d#_W^FE2(W!YumzErzUs=)TgLoWJrHd`#x_ihTIA6wc8V#8-8E2iSw z=xuVA+O8it`~xJd=6|fpTDtb68;}9F^AO<2y`}1qSspSjI`OgSruxL6=NpytUf2<9 zz#3nB<;U-+Z^~9~#>gTcRO_hw&j9d>(FE|~eq_Nbn!`|)rbVw+(3H;IJ6*T*d$|PK z^#aF#FC~VLsk}MEcW-D*!Or^Ae3iGnoNnUEByX#~fF;mf2N#mm_*C}q2n1kH$ zzRhDk#BfZce}xc77=W)%_Oo?t2l3}lL6N$q<7zq=EwG8U{cpZ|{Xv?QN}kr)WQxY! zRJuJP{k!t_YG6d4+E^=dqO~*pRlSE#XZ!fL&B#8f+(_#L*MHkc;?bkfch8(My38ji zoNLP_--p~&Ue?+j*y81Rth4d97&^S~tXzLk`iQ+AF8pCFxtRMR$1lI^(li?Z%3|&8 zF;&}0kz~&L*evDz^!B`8;I&^}ry1=1L4CjFUfKM((Vfb{KWv*MQS%h|b)GY@JU7pw zVYg5-AFW;XYEgFdK?Y$>><+QSF5a=F-SXQVRMQuoKnMP-sy&3>D1kT=1CudJT<|5}c3gKH#6)BX&~ zRCyk-*7bV6eB=VJ1i5`lw+|&_P$cX=1O+MJZ z2KP_gD>QhTQ=$6h4c_$Lr%r5&p1LI2HCYoi-$6D$uO_K|%OR-(I|u!xFCJXm<|+LC zY&cx#Q9%6lO2Kf_<>IVY=MLkAZ>T;J!*-rjHYR@C-so2Rx`h@Z64I2AvFJ-rqwV*t z?$XaCvS&QfuZsR}lY2ViT6-LoO`x@#9&=Ux(($5~wj*!(@Tk2HH4E@f1nKzo1@uJk z9Q8M-;M1q?Hhva&3wIvcDxOc;e&lwHTT8+DHX_|V&Axbc;dpRBEtW&GqwP34Pv`zX z#|2@l{Q?01&y^)&#l2>VQ8siL1osn{H$8(6INUd+>c>DnGs}8}d~mXvdIf9HIjMOA zuz%rHF;Cpbd>V4&!Thw5wrxR6Y;q?X(;?u3<$DgJikb!S0UHiuW>?}mfq#Gn683>d z7BSVEY7}$Wu~64IfX|Z*oh_ac*FWX|oA{(@h9hwa)vbgBHr4B1kf;3KVQU_9n|l2t zBlrf1!lvLop@lvF%Euk3Fa)px**1WzCv<&88za9_8qqybxp=ETzS-(35;mY5l`J&YyJ7*};W z#Ep0&uS8gnnwxLmaT6))8IVE9d7UXojq6jb)QEMN&{1J<2 zv5jz>#F;p&X%4|!&eJZ zMZq%|sk!yt_=)j*wYsVY50@7*zC7~H29Yigfpot8ogPG( z8YwRwMFUr0X=xDh>>$i6PW8)#vkrd^Ox!JRk0;r1{8HE?KSg)?`L&b3zTAItk&leFlEqC>0JU#- zZuItfWoKGKp|=mDQSiH~D>dbq+g@I&EAedZKqKbgowA zXKvfkWrZID7u=QXwHSOl|1nY#{hWcFDh6tM`!BF#mesTZcPF*2WHPDEF;P$%DGq~leY$RZo0u!(|{eDGE0ob7;A99K1RUtr_ni-=iAnp`Hzu@a<>s0NWAoea< zqX}9JvN4@t*m`>p^BbZ+cRw=O)-JEs@Ty4N>L~R1;q=AfY|G*wdo*d>{;p=GZ;7md zx4xjwr%nLp?~H#DLG9UEYk&!$izVV$;}5l8OMhF>nzPED<^28X=c~>duA-74y}&Q3 z;HSN;Vt*7bu*BmF2!j$pR6x2roVq*xp)?cqj2U$FWXW@$7kNeJ>KG!t<$Zg1X@AYT zr^#Q#B)H`#@N5z^W9mRYj*|yCfW{W7jWb}UawgYniaP7I#nMwxZ_}oxyl5MP4j#?4 z4JD;vYDk`XOV^n3hKfmkeU%)`Rk_RHz(_ca8cf~wpoa|7bEg6dq`yrQj1g0a+KIMdWC z%h6Es9t1E4^ul35iO-Q8VpFAVdOpNyAT?PBDcbdW;xVxyFwbUw_?i96BMn_V!Mh#P zIJHZ2sA&LWPzoYrUPf-1J)VLrH=m3KU!KdgTK`g+{%e5^6YU)>Q#ddJqyG{47q!0n zML``z3rO_Y8|Sx9OUB1=#c+Z|V&#X}0QZU0f^C5kntO@W#25wOva46E?sP6;^hUOU z6_0p^d#L5bUKIWo(vwIkMkwIEf_;kB%|9RC7kBy9?k%Zb zq*H5C>UbbD+Nzrvk!@6% zfoWsBNO=P@p{`r*h1IUoVm(w97MOcSa4UIH`iao|F;a`rpV?(J z8r60IEwRO4l0`zu3_mw+x@`L~xK-MPiU)~`B2_#v4dh_A7*HLE7*F4My!L&z%s3q9 z;|e6e2K~^a=l;j18EWZ-L*_`DgSV0LFXt9@8;%FMtYUk>NC&LHG{II zj^DlZD^LcHSYvZ6S?>8QvGR2V4`;(q%clO(Y{QIY^?*?Vk?7gsu_-%da%f=d!Ug!Jdt)(%G(Cg9dE>U}xqb!wFjvuQ&R&0PtsULqZts;|P=sRZTaM~W!7Owe4LV210z?qf)30Fb>0{mQ z5iV~b3)&@YS#Ly(<2L?w4dYyR!XDb>IQCv)*SC_?51x|JaEK^8c_Zu4{=KiGFY{rv ziuUltddbbJM_RQdK*9jcpnLb=dAL!}G&$&*WoZ>=p{hRQ*y*Ne#uCwe2bwqx+!0(V3*>|#WworQg1xOI}R#%D5C@e3abIu z6i7848GL#XPpbsab1@`%R^R{yC#oc(Pkz+p43IXOJdA>k!&xO2 zfaYwhQo*3l6i=VI1s-(;|28)9F+-mcJ=-IMmK$-@t_zA*Ng0d#E)jJ-#Bl8SYx*B| zhbD}@y9Alz!@IKUJFk8#WUf%Jqk?@fn<8Z|g$|CID=q5!)_NX1*n7Dcrukc{*hciI zEg-)NoV|@~uY+T2531@vbn>cC)w=+*an-YVdPwzLs`X72CBUhWi4lKnQpf&q`}R#F z+kVe-kg1zrkZ2PdsrrF=NL|U`6?GG)Yj!<-x@p}Px$js@2I1Sm(BB&d-4~9Z`W*+! z2&9Sfo|+vDJWaRP7C9I5AKTe8!ZIJ)g@N z-SM-@>;gp4U|d3UJk(1|t@*+d3*8hAJF+_i;nNsV`V`Y7V*%)RgfjlXt7{gE&SmfB z+vMfnK?T>4ug~G`h}vB2{SkL<2q5cR$>S$ZpA&MMBT4$_q)?@D@u06uo7NM1$2b43 zs60;J__^zl>)~`0tZr@d^&4o>9+baFn7=gkb`#n$h`dwMEXF{5io1DA1bO}v{s#Wb zIAeS7y|er+mF|O$53kSM>a34)W(pa7M`^LC$^E%6Ul)HIAA%eOy1vFDj4>4eZQWp8Sw{J9;PS29KzndqTYg91bR3N@V!`zK|K?Y9 zPU$?Hx3Vsf%;FLZt0s6kiVb|ihb9-7rT8dT`l6Ny74|P@pGvz^C#WeK+nG(fAWYI51h3u;e~v(sQ7R33ClA= z+C9-f(^h$v9IYsB1#5Oo>ZtQ#M{SJ3qI9WR?A*vzW8|?)dVyB4JBg*#jTRWJKn`lC zXo=O@;8vwG_%W5s;c-3pQg5xKil5vXu2Pabt7o53rC+F3>i-s?aNQMSy7Y0AgR{nJmbdUW9^Mmb5J;wbjK00h(O9u^Peb6fu z%}SAe*8@3Bm{Ns5=O5neXs;SikDpJcmnR{%fbZnce{*?D$SzvP~(jFqIsgsZLvgns`y)ji-Ikaf7|UU_w#WYlld0{h?`-k7G}chs2WDs>G$%LAmm|Ob;dvJ8U>$Nw zQs&%6#>0BLw28TlEe=RS_}U#!^V!Ede$_1Obnr#BDAx|XJw*C>#1P8A%pnnR$7=A> zLANw_MDgw180n+iTq7#G1ID1}v(vS%LnoPl9JY_0-iv`++f*QNPUE#bp`CRmPHj1T zfbeWZxh)QQRIk$mX~4+07=EvWnQ7!MsXUpnTf^P7?a?aeZ6` zJNpTLc6E)ER(WId@g;&VGu;CO6c%E^6%FYPun8;(@9N5)he_zpq@2KF zj}pLrYqFAM248<@G-FwXe7CKYMLk#|$16gNDiSIstu3*)1;w#qUsLa8fUo04BIXtl|z_g_f!&&0Ncea+3ZE1_HDP%Ud9IOf}ypZ4fpCU#>lR? zu$=sA)!NFJR$wm``R!vWYUwS7eHPgsK)|IQ631IgoEjg$;#rNie8zk9{)s%2Xb5^- zoMjXdq0jZzC1&FNQl0Sht!n2KE!E}E5q2qfxJJaU7h>sg;yu{ zF+3I_brT*@v2)G3#syuK-=Jfn*Kpl}`kjBe#?zQ8!?nr}7?=7;TC+It zOunZ`d!FaNpXF6_X-P$OW< zzjgKD#h)-(*$mXKHbv|70r?KTDJB!4>@GCs z-jYKvoRE*@4!@9)6s=|QUcq@pYDSYs&-G8upSeU*#pw-u(kxw)TTwEQwxMP%9I`N- z<+UK)PLAU;a9~*^^ds1hK?&+P1nKmRfa)rkFFIv59;|!L1@G}Vb78CJ#Jtm;&6jH> z2Q#_n(WDfPCl~7BGJ_b8W-AlLt7>O_r|wMCT5G!bB6!%^TZNM2g2zcrnC62ea*xCO zx40g#TOx&-XRtIOX}=NOVT7gR>RoQ~R6eSganuj82G#n|P*9EHD77-SHR&m1aj22V zh@ty1)MUfUaLrh5Di2Z$i4ErvFVQbHWC;ES6yneM?IF*9z#7MkeqrVc;R4p@yj-6% zBZ%4 zIj0_1FGYZKU`$|e80^Mfe4s(nj>$kqXnpwHWVHWW(5Kk4YR$M#W1eGnLQlL~d(~aS zeCo3WW`?U@*8l-q8L#nP>qd6^xRngPiMc07b1$6hBzzegfjTjihy%C^24>`qku2am zfFsB@<8j1u-WqsHLpnbCcZnl7wR?yP%k}i6ckU(cLKx>9M-2<6dNF#{Fjw zW&;#crq7Y!!Q0G24{;$Kz(W1TMg1*Y+Pb%?<<$FDt$$miK|ON~h)sV!<~gVVYs-m* zkx_QNU(P+?CXV>n?mvQPqJ%?`+db6uJ;?EC^Gb4nBjU;vLyuf$AOo&fsL%RUPrL`f zMhSe6DScao@kdnOv+Mg?;dHfb$9TtTq%pg8-c9b>^3@*){EQzkcSDh#pySiat(U}S zvYHpBql|(@Td5~;J&!0IR$NZKWQVvyy-VuwaZHwdon7B`3G<|qo8ImdtGx`QkW)i1l{J?3p9>ZpaP9sS+d=mf{AnH%2=Hv_W z)3=sfRUEn2%fW+6%SP8ENWXq9^@le-Fc>?I8Z-*NPPnLJiiqKCI`*+j6dkdR4(W}Q z+W$zK51vnF-rlid@)1~ac5ZEUr!?8hT>ej&_Ww0jz>)Ru$up3%`Ycb+ocojb>(1}1 z|7oyzp511AlGUL=v_G9w$QdtuSB0J0f>ADO?=S);UZ}mN8jRXFkKGoX|@?j2~ zRYUf!0U2}$VY%`(2ntwz2lfenW8;KN-^f~fbSobNv1)@!4+KS%Wc0y&ThN#{F~4>! zhhbY_?fb_AsyNrrXE{9B&huQ;j^UI%la0ek$)jN|dT%%rD_F|MJoLvz2<(criDLRX zvN{CrKu|O>8p?nLAqArzQLm?_IqQ}b&|#%5Hxg=_S@mR|lnL29Yq4+uB$Auoj!;tn zH*!z|j^@>MV@-KY1p{on+HP!SUCF&y4|`NC1*LJRKZ%a)52suc(hri*!><%H8lQo6BqBSP198%V6TF*GgbGhWtsc#XbZ|@ zPC&uBWf%M`07P&qY$*4IA=GWz_-TwXYCqLg6hNI?oiV^a41N!WC(2)UI4?XVURW>) znZmMKAsBC1f)^e4KG9SFI4X4vcxsoB^4)n5`7FH^O#GDKdEgy5{DZ%C3`Q8jsOy00 zaf%GYF0#T5W#kj?WNi%PBI@ZO3L{3|OFmrk)Ds1wSTWaFP4J3OZ9|J8xE_r(#hAj( zgrst&`Bl-c`l#HoX6H2{$^zBEQf!9arP?9hpr|5-NNCFhkuWN=Gq^+ytR4JNhrx}! zH^^P{qGrh)))uwH5l9=T$$teD4aGyrP-#pYTP52&<3U~-Bbkra6{Pjm>-rzXfZf~! z6@55YI|8}?T_D}QW8T*@`J4g%Lkl_|Kq0y?1`9-$ZR>JGw$eMDN9AY@a5r$$i1R55 zJ1~e#*j9MrzNagay@-Io;ULNiSG{r^C~hgHQE7KSX~E>bN_e7 z&J_6$4gMO+fw2EC=Y-AB_dG+9Bd{jCC)>-A8jws3;6&}5{FwNW$p+HO2)_9`##nn0 z5DlsW=5*I8FDqj~4diy3b({%LvlVLA#(>?_Lq{k{kyWQcaKd~gGb=+4m+Tx*1`hIB zRR;kou#1MW$|1vyDuJzhR~wX>!ei90YTwu>#$0a>-NL zYfycwvFcG9k{vEeNy1M3#rWcM1w2cU-UeKcSGf$~wKZtT_$W@UxJY1KiWR7O5ufkE z0+Tn$7Yk941}FpqJjC((xB0acACEckhTp|WumDpPui+(UnDOphz?jOSY}cEEku3=b z+u1R!`eXKcG=GSn}|>?yAkd+i=!XLm)1=I1<+zv^qd?y83aYr#3+D%+^( z(Z`9tqAH`z)?$&5j&UN%F`SU};U=!%n$NxHiZr=6&}To*b^*p>%w>qrdLkmy!yb&y z3z{TgCVZxtqi%4{sC5lCFek3D%s`V2EDb2C@sn`>J&Xb|kau0G9%ib5Fs+@~#b>+CMqt>H#Ap7WPl09MDp5`qrB%-tSft84(#+s^72?S);b;uXL(w^Ugn@ zz?;TIu$ZCX6rIZqB17QVrn!nRfsm{f!5fJNQ&7uX_Czu&7-iBp=M|08q20nyFoTT^ zVPBAatN}eK#KV?%g-lg4i1ZADGRr26g3&sczI>;T+ri^m`wO0K^Nxu!b?Y$}U@3S3 zGHN9?-P>Qn?7KBzrNldgOPtH*KPk2lp>?L@>o6qv8gSb2$o>NwPL~rO>eD=&lnmws za1O=;o|54c;4>GETuFs+Bq+a9HRl0r+}FbhA;ZwIY^vxL>{ba*v-J2(g-{%2QAi+| zDkwG;f>hh1G-`U>b?WiMLTK{3=EXT}tw84(x1kE%bFyR567IK$P>ZQMY(MK77l*CS z#QiP*L;W-M7xSO|ndvhR{$u$I|I_=&zq4uS$Hh*ad-krp*ZFDJSM$>#tlmM>QIbl7 zd4b0kp~s6_&m6i%bfVsO`9dF0Qz|mFux)P-jpbKI$KkcT?sb!PLSJBw`HBL2jw3zx zX$Fbku*$jCS$#g&PH$RAD*8&YhR>qo@e=f;`_-9xepc!S0U*4;xn$sKNq|$Dacf}U z6kP}C>2y(z5ug$JAoUpQ7-ZkGd6g2C5l30{%z1vX+K+c&`6b_QXI|ar#kP& z4RRY#$a^x|qrUzFk|s=lZQRmt=uOUtK8c{jI#V#0-Z!hB3kaopr5c-GZJ_ha2(4A8WoNo7-Rc^jX8A3=OgwKfcYL& zyVbA@qXe&U&L3RofdfQMSQBIJn)o~S=ekguklv_nzNqh+CkPM}$s(AJa5KwPb2Dde zj#`53qtXYAjKnG>EBb03k!?kUT2lk;1HJ?q&wS-NOZ1 z7zvJV&jh&DO$zClj#otns*ThcQduNBbz|>{zPL|&y`@lIp4A#Jn|>&=kf8#$tdaYX zguU|qe0jbO{zal|;_p2X(_tOZ$KmmOpSyzcn>73hv8qy^oLTB89Ssm%eEz>hTcg}{ z=J5XO#a*kXT0hZzU`W3Lq~IPoJdO;*0>`})u3GUn=#1aWCiLHb718G+*9#|BSjjvn z0OJ;})ML_%EQ(#_#v6~C@#q*K{EWbIv zWeh>jU#${S2t5wYT7qy+`Mg#12^M)gcq!d^U;udo$RY+C&=h&Gt9!Sxlhg6wa{(g$ zVVZs=&<*@>Q5T`mQu5~{hD9UYgvx1p2GGhSNZr>8V51Jifh<6Fcto5jI|2Yvj6jh; zQ6(FYEyI$@$qZm@E2FGxit7e%KdO@Rul^ku1@s?%bLT$%X0rV7|AwW!+HET#AUs~A z#H9-bUxp=#csOE(B>%T8KTJ*qGSUh9JP(;Xl^}d2wv|MHUXQbOdl<9*U^`Zxs*OzM zao5Xfj(j^4*v^XMTuP^{aY|VRI8x4y%15wnwyaGdKs#t8q@pJm_@|(e;n`ZZI9y%1 z7?ZS6ikwg*W$Rk1;k(IVYn32*=jdpzct0DQLZMSXcwQ-|b#J-cvG(^>7AlQwnZ?Ym z`wl8zRK20ABkJ!BbkTGhv+jg;=mq1PTXI%1Vl#>PQwy>l_|{o{ReGe9ZTN!LcvV*DV_99-3I=jB`b1z=+1&waoi?F zh&tJVuu8s>!o`BAd)-!Eg#o?efxZCl(cqu_X2jQ1PiamD>SGAravJZ`4^iXO^2|kb z;^8qV(LCciATR(ZS!gMgF&O0b1+#e@>KxbXVFjY{5|it7+S!aDC+J9&tL3u%`uXl?Th=!7)u09 z$yBPqqP>lZUxF7xv}n4cSKdJTbQHQY`-h%Wa2}qFy00$KXGdwkDB6=Q1x7n`Y z*~+S=D-~BW3%0siyiYTO%?%`O^Hs2rR>ju0GGL>DzuEU+%+csm9^lplaCeYLvZ`%L zrNUPwhwsDK54-=|>mL4g@nXM|ZNjNae(9~idZBtZkPaLo2f8)~yKmT<694{UYe(0$ zJbyNWNu?>1!xu-5A~PFrM`Jknl@*Q9zr|L`8 zQ&ZPuFmAgg++Y?YY{$da!vl1~K>5|i=d5o$E1u#n95LJB=s=VDnDF!Xm=jLn0k9uizim9wH*D z3qoekAdJ%Ukrq-kb12dqP<8To)}#?%Ja|L)FoK=2Ylfh^S8)?*? z>1oQBLUje=r-nn7U=>+(e4qY4LrBNWD|Ide0rYEp+Prl1rAbJ(z>5}7A*POeh~IJP z60Oup9LUz^92D8O_~kc`(r~mq;4FN0%;TcLH~bpwj;%<>S%dfPVoND&kbC2cjm*<#cY^LWolD8@ zkb{~9NONvI9-Z8JNkXG_!U#00qHkz%0hj8gEQx*SkW^4DNg`X@s<7Rryf^SJT(eVDrfQYwZR?C-p=3vm`ngMI#=K6hHW;c-$v?VG&bqmKMYuqvQ-9q`4!sYl}GPE z6p88~l^c!KbLa*Sliy_Pw>GrSS!I0KJv1@4AXs8fHIMl2`3sOWpL*d#@r8ORR62Bcer=HK}+H%9h#Ki$I&Veh=<_hSj%VQ3wv1= zDJxtYgI0&4axX0N#VE$j3vQA}(i$eHZM^Q5>UGMz!nSF~xg*4E)ul%1mw+7F5?1{W z3RxkXmTb}eJf(`qF?kp?Xku(`Cme{BI^FxslI4*n3#>DXvk3DB12Xag*&3BdZ*NN? zkQB)Jh;C>1*dh z5Pd2oDs$lAka5U}vPg0w(C`(F329fInlnq!<1cWlC0Fl^jml>oj>L~=8$8}_#3|J1 zxxZ{4j!QKz;u>d$c8*^oGYS%s>zEBNu%taw)e7DO=SVi0(qGcBvQ)(vHH=#u=2o>9 z_1U?&*n!FNKN#mQ;Y7rSmCNXCK15&uPH6ggz2}gpd~8TwtWteNBI&Fz)%BbXM|OKp z22Lg><4>m@+;TKNRR1m0upnz&otR>eR%p##-osz6TO98${a@X5VBv0x)v8p;e0qL* zaeR0|S)1^=oY@IuLFrC&P@yrS@Tx>7SseSO@h6I!U?JTOT@m$5r+FWtO$l9mF1+`n zp5L9~q3^Lq%l*BUX3_m`CsXR{)K^I@b!MI1wuN=HfYBuvq^`+=6;4e=Rj~p!%*aDG=R(MQc8;fYWO$sFyX&_kiGoow z*Z18#3*`yDy{4v*b4EWuz5$!X%YNR3P2sm`Pw+uwjoWPo0Bg}JW5VL8|H>*JM&%^@ zX!^@=tB}MOdbIVoV%`~5N}9)`%b@C*r+4d_UAj1B{l<{qyqjqWCj!C z&ntX;sA5dwLiuy*+Ok07N<>q-aS7VL zu1MRNc-r5JR{7m29y1+cj818iak{LXxr%WGn`*BP)Rl(^eDNAXdHo;XMH|L(?nbBN zI~M)6P8SUfNj9FZ8M`ApS@0M}4TZ>$J$xhP)&wq%wC-`p;3Orr zTtdfI=O?gM!ndN>Cq}(RRk1_KrD>CF@B(Fk~#OymE#=sVOR6tTF=N%nA$~zt|2$Q3esH4F1 zsm-nrrex9**_v!=-Gz~Ow{_dQ%5l>*>tSqptYVk<_o612E;$<7U54CcPq(lS$_=y% z(CtHCLu@jy8|nA;7MCK(zW1$=XfyyXKtt5IjUGft^0zM`7Hh*4rf3!v;`R}q0 zLVw*H#&DZ;T=Mpt6g&S|59+|8ag8oXW=4J#_1D3sPlf2B?=I~N zyAACWL(P29o*T<2er-xQz29r{2Y|Zg03MC>HN0`RAtHux;Z=P{S+;$$`e)3m<-2vu zwufwU$<&AXVnG#U^G^!!If54!hmEX2rxao?kwP}Adg=r4+W7=^g3_l^%wWISp7mF7 zxw}{i{GM%>S)P-AKPZgeCGz~Faj=9=FgGGw4NRN+XL4Sc38w-#bg}j|XwlC*#qeA2sm>b6dy}h(Ssfz5OdJ?NPwnnvI=6(G=8%xPQq32gcAkm5I{W2hJMh(dg&)GXLlBA?4-~$yTS9$*>;};ZUQXLv%VHHz)|CT5F z$7Md7#*fm9P;U+%jrpyX4G*ae%WsM+jJ#!kY)7j$z^QFV9<1>)D z?$hQh2E}8PM0{Ld3pjvI9Lt4Ks(t}=>=k>rMWpvmPqGKH2>!VIfyvT^wIL*EzR*#L z4>}b_3@(xA`Nepg7Z>5ed!hhyylgv4$v@TI8k#&C-#agl7a)gquRE}3JYWH51tO}n z)S3iW!fRWeg?NmHxdqw*;OLQ1-CT>rX$Tq>fIo7re#E^nXZb!i!d2v5!|I)!NZyy5 zQyr>#+W&?I2kJAUv}*^KoMr~R<8d6|QdS>|Q;HXCF72!>$UGK^ufbsC+gd%HZh#Jr z)p8v<-ZXxDa9$HJ*pxh&;S|v7WN4aQKj_ULJT{K`5$J)hLMz>Q1{lQaW79d8L?z=4 z^J)v(s?kI8W9$&bHUYtOo72~|&qWRNN&ZfiKVkPni)s!Nab)hmVj?4^LjrK~8qP+nt)IY#!A6bXzI z#DnD}TneJWusCTxS`WQbm}dH%)PTC5YART#Hd2z<1Vmqg=bvaA)F3Q?^R_s4(EGGg zRCWRnIL;Pvk)4yaxysqB5OE$xw_@*3A)@KO4qieD_0=l`!ZPTnBE=Gb*E)u%eqq|N zc5b@D>KmUbjk>matGZu$AN|~V$%l0*f+O$S7gs@#GbL^OyBDV-k{l>krt2V${84(( zd6Ob&9tL(Cdf+JyGi%ThH@vM5i5FhK8E(42bq0(w8ZFctrPo1lv7ybJd|ja@$DHAF zy?SVTc!2by(@0QfQKNeO9eZyhaiYnt@VqJK;Ez5yvU81~=X!+9pc%eh~qTr z3Ku}rzN$Ra#WTMi8nmcIM}L$dMfD0ksUrT6(V3Pglxjy+~epD!&R!uDB%pR?&eB{YFRNl-)ZeEIGNeO$V_ zhRc3IiV1#ZfJ4N2`TjON-ib_v*z|2`^uitL&>E8xg3l~eEcM;#iyxRqaNK-LorznB zfgP~a1Tvrzf0ekEUM<=nL*9(G@!VLo8D@>5GacXEOofX0Q^Z|cke#$C=fbAZ|Bbfd z1=dpyI!3PR!yoNL=lbphzoY^z>en;jsSSpVne7L8J7?@GD%}j4^Hd?ThYnS5nDz!G zhV{e)ub#K!z7yK&R!`5pngF!23BV?v_l$=IAULJnV)zKB8*>N0yXBw?mjWmAE(y{z zF}!U0rD_UK0AJ`@t19u;&eeMN2%5~N1-Qx_OuVkN%*fD)=iI)h#;zCxI|Fkg>kc+Q zuxfPT$=o%5)n-L=_;{Jtt_!9fsc7rTmwOKtJY_r-eKY{$WcBdbWOSl=R;H~efPM$p zj}*P=SQ*`GA^KeN71`WJ6dYH^o7?Z_#8(1(jCaZB?c?O`8LZZXXp@ut>EPT2zK}-a z=Nsjh2ulP9XoMAB>VH|p*F9Xd%_Lmqv%Z@Jp(#|MvqD@*Ae-KB3iX5RPN^ZYm&==p3aH&3~fk=Oo>FeEJCaf_N%)x;L5Z^qCS&y0xC8!nD>7OE2|U1P20SFLEU25oN|OBr7J8{-Aw z&=@>%71P-oavy)xrNX6`A+fRpUL>Hq9p8z3S^S!hke&4M;j6eNi*M6XU(dg24o%^P&aSMBp3N_=X4HK?Una->mt#W(PvF`
x(S56%X*v{GcN-$k7B+zmL% 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 None: """Handle async setup of instance.""" @@ -161,7 +186,6 @@ class PlayerQueue: """ Play media item(s) on the given queue. - :param queue_id: queue id of the PlayerQueue to handle the command. :param uri: uri(s) that should be played (single item or list of uri's). :param queue_opt: QueueOption.PLAY -> Insert new items in queue and start playing at inserted position @@ -241,6 +265,75 @@ class PlayerQueue: await self.append(queue_items) return self._stream_url + async def play_alert( + self, uri: str, announce: bool = False, volume_adjust: int = 10 + ) -> str: + """ + Play given uri as Alert on the queue. + + uri: Uri that should be played as announcement, can be Music Assistant URI or plain url. + announce: Prepend the (TTS) alert with a small announce sound. + volume_adjust: Adjust the volume of the player by this percentage (relative). + """ + if self._snapshot: + self.logger.debug("Ignore play_alert: already in progress") + return + + # create snapshot + await self.snapshot_create() + + queue_items = [] + + # prepend annnounce sound if needed + if announce: + queue_items.append(QueueItem.from_url(ALERT_ANNOUNCE_FILE, "alert")) + + # parse provided uri into a MA MediaItem or Basic QueueItem from URL + try: + media_item = await self.mass.music.get_item_by_uri(uri) + queue_items.append(QueueItem.from_media_item(media_item)) + except MusicAssistantError as err: + # invalid MA uri or item not found error + if uri.startswith("http"): + # a plain url was provided + queue_items.append(QueueItem.from_url(uri, "alert")) + else: + raise MediaNotFoundError(f"Invalid uri: {uri}") from err + + # append silence track, we use this to reliably detect when the alert is ready + silence_url = self.mass.streams.get_silence_url(600) + queue_items.append(QueueItem.from_url(silence_url, "alert")) + + # load queue with alert sound(s) + await self.load(queue_items) + # set new volume + new_volume = self.player.volume_level + ( + self.player.volume_level / 100 * volume_adjust + ) + await self.player.volume_set(new_volume) + + # wait for the alert to finish playing + alert_done = asyncio.Event() + + def handle_event(evt: MassEvent): + if ( + self.current_item + and self.current_item.uri == silence_url + and self.elapsed_time + ): + alert_done.set() + + unsub = self.mass.subscribe( + handle_event, EventType.QUEUE_TIME_UPDATED, self.queue_id + ) + try: + await asyncio.wait_for(alert_done.wait(), 120) + finally: + + unsub() + # restore queue + await self.snapshot_restore() + async def stop(self) -> None: """Stop command on queue player.""" # redirect to underlying player @@ -296,20 +389,58 @@ class PlayerQueue: """Resume previous queue.""" resume_item = self.current_item resume_pos = self._current_item_elapsed_time - if resume_item and resume_pos > (resume_item.duration * 0.8): - # track is already played for > 80% - skip to next + if ( + resume_item + and resume_item.duration + and resume_pos > (resume_item.duration * 0.9) + ): + # track is already played for > 90% - skip to next resume_item = self.next_item resume_pos = 0 if resume_item is not None: - await self.play_index(resume_item.item_id, resume_pos) + await self.play_index(resume_item.item_id, resume_pos, 5) else: self.logger.warning( "resume queue requested for %s but queue is empty", self.queue_id ) + async def snapshot_create(self) -> None: + """Create snapshot of current Queue state.""" + self._snapshot = QueueSnapShot( + powered=self.player.powered, + state=self.player.state, + volume_level=self.player.volume_level, + items=self._items, + index=self._current_index, + position=self._current_item_elapsed_time, + ) + + async def snapshot_restore(self) -> None: + """Restore snapshot of Queue state.""" + assert self._snapshot, "Create snapshot before restoring it." + # clear queue first + await self.clear() + # restore volume + await self.player.volume_set(self._snapshot.volume_level) + # restore queue + await self.update(self._snapshot.items) + self._current_index = self._snapshot.index + if self._snapshot.state in (PlayerState.PLAYING, PlayerState.PAUSED): + await self.resume() + if self._snapshot.state == PlayerState.PAUSED: + await self.pause() + if not self._snapshot.powered: + await self.player.power(False) + # reset snapshot once restored + self._snapshot = None + async def play_index( - self, index: Union[int, str], seek_position: int = 0, passive: bool = False + self, + index: Union[int, str], + seek_position: int = 0, + fade_in: int = 0, + passive: bool = False, ) -> None: """Play item at index (or item_id) X in queue.""" if self.player.use_multi_stream: @@ -323,6 +454,7 @@ class PlayerQueue: self._current_index = index self._next_start_index = index self._next_start_pos = int(seek_position) + self._next_fadein = fade_in # send stream url to player connected to this queue self._stream_url = self.mass.streams.get_stream_url( self.queue_id, content_type=self._settings.stream_type @@ -550,7 +682,7 @@ class PlayerQueue: await self.play_index(self._current_index + 2) raise err - async def queue_stream_start(self) -> Tuple[int, int]: + async def queue_stream_start(self) -> Tuple[int, int, int]: """Call when queue_streamer starts playing the queue stream.""" start_from_index = self._next_start_index self._current_item_elapsed_time = 0 @@ -560,7 +692,9 @@ class PlayerQueue: self._index_in_buffer = start_from_index seek_position = self._next_start_pos self._next_start_pos = 0 - return (start_from_index, seek_position) + fade_in = self._next_fadein + self._next_fadein = 0 + return (start_from_index, seek_position, fade_in) async def queue_stream_next(self, cur_index: int) -> int | None: """Call when queue_streamer loads next track in buffer.""" @@ -633,13 +767,11 @@ class PlayerQueue: if not queue_track.streamdetails: track_time = elapsed_time_queue - total_time break - track_duration = ( - queue_track.streamdetails.seconds_played or queue_track.duration - ) - if elapsed_time_queue > (track_duration + total_time): + track_seconds = queue_track.streamdetails.seconds_played + if elapsed_time_queue > (track_seconds + total_time): # total elapsed time is more than (streamed) track duration # move index one up - total_time += track_duration + total_time += track_seconds queue_index += 1 else: # no more seconds left to divide, this is our track @@ -655,6 +787,9 @@ class PlayerQueue: try: self._items = [QueueItem.from_dict(x) for x in queue_cache["items"]] self._current_index = queue_cache["current_index"] + self._current_item_elapsed_time = queue_cache.get( + "current_item_elapsed_time", 0 + ) except (KeyError, AttributeError, TypeError) as err: self.logger.warning( "Unable to restore queue state for queue %s", @@ -670,5 +805,6 @@ class PlayerQueue: { "items": [x.to_dict() for x in self._items], "current_index": self._current_index, + "current_item_elapsed_time": self._current_item_elapsed_time, }, ) diff --git a/music_assistant/models/queue_item.py b/music_assistant/models/queue_item.py index b5dba9cf..61c5ded7 100644 --- a/music_assistant/models/queue_item.py +++ b/music_assistant/models/queue_item.py @@ -46,12 +46,11 @@ class QueueItem(DataClassDictMixin): return d @classmethod - def from_url(cls, url: str) -> QueueItem: + def from_url(cls, url: str, name: Optional[str] = None) -> QueueItem: """Create QueueItem from plain url.""" return cls( uri=url, - name=url, - duration=None, + name=name or url, media_type=MediaType.UNKNOWN, image=None, media_item=None, -- 2.34.1