@@ -6,7 +6,7 @@ use std::{
66 io:: { self , Read , Seek , SeekFrom } ,
77 sync:: {
88 atomic:: { AtomicBool , AtomicUsize , Ordering } ,
9- Arc ,
9+ Arc , OnceLock ,
1010 } ,
1111 time:: Duration ,
1212} ;
@@ -55,42 +55,75 @@ impl From<AudioFileError> for Error {
5555 }
5656}
5757
58- /// The minimum size of a block that is requested from the Spotify servers in one request.
59- /// This is the block size that is typically requested while doing a `seek()` on a file.
60- /// The Symphonia decoder requires this to be a power of 2 and > 32 kB.
61- /// Note: smaller requests can happen if part of the block is downloaded already.
62- pub const MINIMUM_DOWNLOAD_SIZE : usize = 64 * 1024 ;
63-
64- /// The minimum network throughput that we expect. Together with the minimum download size,
65- /// this will determine the time we will wait for a response.
66- pub const MINIMUM_THROUGHPUT : usize = 8 * 1024 ;
67-
68- /// The ping time that is used for calculations before a ping time was actually measured.
69- pub const INITIAL_PING_TIME_ESTIMATE : Duration = Duration :: from_millis ( 500 ) ;
70-
71- /// If the measured ping time to the Spotify server is larger than this value, it is capped
72- /// to avoid run-away block sizes and pre-fetching.
73- pub const MAXIMUM_ASSUMED_PING_TIME : Duration = Duration :: from_millis ( 1500 ) ;
58+ #[ derive( Clone ) ]
59+ pub struct AudioFetchParams {
60+ /// The minimum size of a block that is requested from the Spotify servers in one request.
61+ /// This is the block size that is typically requested while doing a `seek()` on a file.
62+ /// The Symphonia decoder requires this to be a power of 2 and > 32 kB.
63+ /// Note: smaller requests can happen if part of the block is downloaded already.
64+ pub minimum_download_size : usize ,
65+
66+ /// The minimum network throughput that we expect. Together with the minimum download size,
67+ /// this will determine the time we will wait for a response.
68+ pub minimum_throughput : usize ,
69+
70+ /// The ping time that is used for calculations before a ping time was actually measured.
71+ pub initial_ping_time_estimate : Duration ,
72+
73+ /// If the measured ping time to the Spotify server is larger than this value, it is capped
74+ /// to avoid run-away block sizes and pre-fetching.
75+ pub maximum_assumed_ping_time : Duration ,
76+
77+ /// Before playback starts, this many seconds of data must be present.
78+ /// Note: the calculations are done using the nominal bitrate of the file. The actual amount
79+ /// of audio data may be larger or smaller.
80+ pub read_ahead_before_playback : Duration ,
81+
82+ /// While playing back, this many seconds of data ahead of the current read position are
83+ /// requested.
84+ /// Note: the calculations are done using the nominal bitrate of the file. The actual amount
85+ /// of audio data may be larger or smaller.
86+ pub read_ahead_during_playback : Duration ,
87+
88+ /// If the amount of data that is pending (requested but not received) is less than a certain amount,
89+ /// data is pre-fetched in addition to the read ahead settings above. The threshold for requesting more
90+ /// data is calculated as `<pending bytes> < PREFETCH_THRESHOLD_FACTOR * <ping time> * <nominal data rate>`
91+ pub prefetch_threshold_factor : f32 ,
92+
93+ /// The time we will wait to obtain status updates on downloading.
94+ pub download_timeout : Duration ,
95+ }
7496
75- /// Before playback starts, this many seconds of data must be present.
76- /// Note: the calculations are done using the nominal bitrate of the file. The actual amount
77- /// of audio data may be larger or smaller.
78- pub const READ_AHEAD_BEFORE_PLAYBACK : Duration = Duration :: from_secs ( 1 ) ;
97+ impl Default for AudioFetchParams {
98+ fn default ( ) -> Self {
99+ let minimum_download_size = 64 * 1024 ;
100+ let minimum_throughput = 8 * 1024 ;
101+ Self {
102+ minimum_download_size,
103+ minimum_throughput,
104+ initial_ping_time_estimate : Duration :: from_millis ( 500 ) ,
105+ maximum_assumed_ping_time : Duration :: from_millis ( 1500 ) ,
106+ read_ahead_before_playback : Duration :: from_secs ( 1 ) ,
107+ read_ahead_during_playback : Duration :: from_secs ( 5 ) ,
108+ prefetch_threshold_factor : 4.0 ,
109+ download_timeout : Duration :: from_secs (
110+ ( minimum_download_size / minimum_throughput) as u64 ,
111+ ) ,
112+ }
113+ }
114+ }
79115
80- /// While playing back, this many seconds of data ahead of the current read position are
81- /// requested.
82- /// Note: the calculations are done using the nominal bitrate of the file. The actual amount
83- /// of audio data may be larger or smaller.
84- pub const READ_AHEAD_DURING_PLAYBACK : Duration = Duration :: from_secs ( 5 ) ;
116+ static AUDIO_FETCH_PARAMS : OnceLock < AudioFetchParams > = OnceLock :: new ( ) ;
85117
86- /// If the amount of data that is pending (requested but not received) is less than a certain amount,
87- /// data is pre-fetched in addition to the read ahead settings above. The threshold for requesting more
88- /// data is calculated as `<pending bytes> < PREFETCH_THRESHOLD_FACTOR * <ping time> * <nominal data rate>`
89- pub const PREFETCH_THRESHOLD_FACTOR : f32 = 4.0 ;
118+ impl AudioFetchParams {
119+ pub fn set ( params : AudioFetchParams ) -> Result < ( ) , AudioFetchParams > {
120+ AUDIO_FETCH_PARAMS . set ( params )
121+ }
90122
91- /// The time we will wait to obtain status updates on downloading.
92- pub const DOWNLOAD_TIMEOUT : Duration =
93- Duration :: from_secs ( ( MINIMUM_DOWNLOAD_SIZE / MINIMUM_THROUGHPUT ) as u64 ) ;
123+ pub fn get ( ) -> & ' static AudioFetchParams {
124+ AUDIO_FETCH_PARAMS . get_or_init ( AudioFetchParams :: default)
125+ }
126+ }
94127
95128pub enum AudioFile {
96129 Cached ( fs:: File ) ,
@@ -183,6 +216,7 @@ impl StreamLoaderController {
183216
184217 if let Some ( ref shared) = self . stream_shared {
185218 let mut download_status = shared. download_status . lock ( ) ;
219+ let download_timeout = AudioFetchParams :: get ( ) . download_timeout ;
186220
187221 while range. length
188222 > download_status
@@ -191,7 +225,7 @@ impl StreamLoaderController {
191225 {
192226 if shared
193227 . cond
194- . wait_for ( & mut download_status, DOWNLOAD_TIMEOUT )
228+ . wait_for ( & mut download_status, download_timeout )
195229 . timed_out ( )
196230 {
197231 return Err ( AudioFileError :: WaitTimeout . into ( ) ) ;
@@ -297,7 +331,7 @@ impl AudioFileShared {
297331 if ping_time_ms > 0 {
298332 Duration :: from_millis ( ping_time_ms as u64 )
299333 } else {
300- INITIAL_PING_TIME_ESTIMATE
334+ AudioFetchParams :: get ( ) . initial_ping_time_estimate
301335 }
302336 }
303337
@@ -395,14 +429,16 @@ impl AudioFileStreaming {
395429 trace ! ( "Streaming from {}" , url) ;
396430 }
397431
432+ let minimum_download_size = AudioFetchParams :: get ( ) . minimum_download_size ;
433+
398434 // When the audio file is really small, this `download_size` may turn out to be
399435 // larger than the audio file we're going to stream later on. This is OK; requesting
400436 // `Content-Range` > `Content-Length` will return the complete file with status code
401437 // 206 Partial Content.
402438 let mut streamer =
403439 session
404440 . spclient ( )
405- . stream_from_cdn ( & cdn_url, 0 , MINIMUM_DOWNLOAD_SIZE ) ?;
441+ . stream_from_cdn ( & cdn_url, 0 , minimum_download_size ) ?;
406442
407443 // Get the first chunk with the headers to get the file size.
408444 // The remainder of that chunk with possibly also a response body is then
@@ -490,9 +526,10 @@ impl Read for AudioFileStreaming {
490526 return Ok ( 0 ) ;
491527 }
492528
529+ let read_ahead_during_playback = AudioFetchParams :: get ( ) . read_ahead_during_playback ;
493530 let length_to_request = if self . shared . is_download_streaming ( ) {
494531 let length_to_request = length
495- + ( READ_AHEAD_DURING_PLAYBACK . as_secs_f32 ( ) * self . shared . bytes_per_second as f32 )
532+ + ( read_ahead_during_playback . as_secs_f32 ( ) * self . shared . bytes_per_second as f32 )
496533 as usize ;
497534
498535 // Due to the read-ahead stuff, we potentially request more than the actual request demanded.
@@ -515,11 +552,12 @@ impl Read for AudioFileStreaming {
515552 . map_err ( |err| io:: Error :: new ( io:: ErrorKind :: BrokenPipe , err) ) ?;
516553 }
517554
555+ let download_timeout = AudioFetchParams :: get ( ) . download_timeout ;
518556 while !download_status. downloaded . contains ( offset) {
519557 if self
520558 . shared
521559 . cond
522- . wait_for ( & mut download_status, DOWNLOAD_TIMEOUT )
560+ . wait_for ( & mut download_status, download_timeout )
523561 . timed_out ( )
524562 {
525563 return Err ( io:: Error :: new (
0 commit comments