raise asyncio.CancelledError()
return resp
- async def __stream_queue__old_usingfile(self, player_id, buffer, cancelled):
- ''' start streaming radio from provider '''
- # stream audio with sox
- queue_tracks = await self.mass.player.player_queue(player_id, 0, 1000)
- sample_rate = self.mass.config['player_settings'][player_id]['max_sample_rate']
- fade_length = self.mass.config['player_settings'][player_id]["crossfade_duration"]
- pcm_args = 'raw -b 32 -c 2 -e signed-integer -r %s' % sample_rate
- args = 'sox -t %s - -t flac -C 2 -' % pcm_args
- sox_proc = await asyncio.create_subprocess_shell(args,
- stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE)
-
- async def fill_buffer():
- while not sox_proc.stdout.at_eof():
- chunk = await sox_proc.stdout.read(256000)
- if not chunk:
- break
- await buffer.put(chunk)
- await sox_proc.wait()
- await buffer.put('') # indicate EOF
- LOGGER.info("streaming of queue for player %s completed" % player_id)
- asyncio.create_task(fill_buffer())
-
- last_fadeout_data = None
- for queue_track in queue_tracks:
-
- while buffer.qsize() > 5 and not cancelled.is_set():
- await asyncio.sleep(1)
- if cancelled.is_set():
- break
-
- params = urllib.parse.parse_qs(queue_track.uri.split('?')[1])
- track_id = params['track_id'][0]
- provider = params['provider'][0]
- LOGGER.info("Stream queue track: %s - %s" % (track_id, queue_track.name))
- temp_file = await self.__get_raw_audio(track_id, provider, sample_rate)
-
- # get fade in part
- args = 'sox -t sox %s -t %s - trim 0 %s fade t %s' % (temp_file.name, pcm_args, fade_length, fade_length)
- process = await asyncio.create_subprocess_shell(args,
- stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE)
- fade_in_part, stderr = await process.communicate()
- LOGGER.debug("Got %s bytes in memory for fadein_part after sox" % len(fade_in_part))
- if last_fadeout_data:
- # perform crossfade with previous fadeout samples
- fadeinfile = MemoryTempfile(fallback=True).NamedTemporaryFile(buffering=0)
- fadeinfile.write(fade_in_part)
- fadeoutfile = MemoryTempfile(fallback=True).NamedTemporaryFile(buffering=0)
- fadeoutfile.write(last_fadeout_data)
- args = 'sox -m -v 1.0 -t %s %s -v 1.0 -t %s %s -t %s -' % (pcm_args, fadeoutfile.name, pcm_args, fadeinfile.name, pcm_args)
- process = await asyncio.create_subprocess_shell(args,
- stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE)
- crossfade_part, stderr = await process.communicate(fade_in_part)
- LOGGER.debug("Got %s bytes in memory for crossfade_part after sox" % len(crossfade_part))
- sox_proc.stdin.write(crossfade_part)
- await sox_proc.stdin.drain()
- fadeinfile.close()
- fadeoutfile.close()
- else:
- # simply put the fadein part in the final file
- sox_proc.stdin.write(fade_in_part)
- await sox_proc.stdin.drain()
-
- # get middle frames (main track without the fade-in and fade-out)
- args = 'sox -t sox %s -t %s - trim %s -%s' % (temp_file.name, pcm_args, fade_length, fade_length)
- process = await asyncio.create_subprocess_shell(args,
- stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE)
- middle_part, stderr = await process.communicate()
- LOGGER.debug("Got %s bytes in memory for middle_part after sox" % len(middle_part))
- sox_proc.stdin.write(middle_part)
- await sox_proc.stdin.drain()
-
- # get fade out part (all remaining chunks of 1 second)
- args = 'sox -t sox %s -t %s - reverse trim 0 %s fade t %s reverse ' % (temp_file.name, pcm_args, fade_length, fade_length)
- process = await asyncio.create_subprocess_shell(args,
- stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE)
- fade_out_part, stderr = await process.communicate()
- LOGGER.debug("Got %s bytes in memory for fade_out_part after sox" % len(fade_out_part))
- last_fadeout_data = fade_out_part
- # close temp file
- temp_file.close()
- # end of queue reached, pass last fadeout bits to final output
- if last_fadeout_data:
- sox_proc.stdin.write(last_fadeout_data)
- await sox_proc.stdin.drain()
-
- async def __get_raw_audio__old_usingfile(self, track_id, provider, sample_rate=96000):
- ''' get raw pcm data for a track upsampled to given sample_rate packed as wav '''
- temp_audiofile = MemoryTempfile(fallback=True).NamedTemporaryFile(buffering=0)
- cachefile = self.__get_track_cache_filename(track_id, provider)
- if self.mass.config['base']['http_streamer']['volume_normalisation']:
- gain_correct = await self.__get_track_gain_correct(track_id, provider)
- else:
- gain_correct = -6 # always need some headroom for upsampling and crossfades
- if os.path.isfile(cachefile):
- # we have a cache file for this track which we can use
- # always convert to 64 bit floating point to do any processing/effects
- args = 'sox -V3 -t flac "%s" -t sox %s vol %s dB rate -v %s' % (cachefile, temp_audiofile.name, gain_correct, sample_rate)
- process = await asyncio.create_subprocess_shell(args)
- else:
- # stream from provider
- # always convert to 64 bit floating point to do any processing/effects
- input_content_type = await self.mass.music.providers[provider].get_stream_content_type(track_id)
- assert(input_content_type)
- args = 'sox -V3 -t %s - -t sox %s vol %s dB rate -v %s' % (input_content_type, temp_audiofile.name, gain_correct, sample_rate)
- process = await asyncio.create_subprocess_shell(args,
- stdin=asyncio.subprocess.PIPE)
- asyncio.get_event_loop().create_task(
- self.__fill_audio_buffer(process.stdin, track_id, provider, input_content_type))
- await process.wait()
- LOGGER.debug("__get_pcm_audio for track_id %s completed" % track_id)
- return temp_audiofile
-
async def __stream_queue(self, player_id, buffer, cancelled):
''' start streaming all queue tracks '''
# TODO: get correct queue index and implement reporting of position
- queue_tracks = await self.mass.player.player_queue(player_id, 0, 1000)
sample_rate = self.mass.config['player_settings'][player_id]['max_sample_rate']
fade_length = self.mass.config['player_settings'][player_id]["crossfade_duration"]
pcm_args = 'raw -b 32 -c 2 -e signed-integer -r %s' % sample_rate
if not chunk:
break
await buffer.put(chunk)
- await buffer.put('') # indicate EOF
+ await buffer.put(b'') # indicate EOF
asyncio.create_task(fill_buffer())
last_fadeout_data = None
- for queue_track in queue_tracks:
-
- while buffer.qsize() > 5 and not cancelled.is_set():
- await asyncio.sleep(1)
- if cancelled.is_set():
+ while True:
+ # get current track in queue
+ queue_tracks = await self.mass.player.player_queue(player_id, 0, 10000)
+ player = await self.mass.player.player(player_id)
+ queue_index = player.cur_queue_index
+ try:
+ queue_track = queue_tracks[queue_index]
+ except IndexError:
+ LOGGER.info("queue index out of range or end reached")
break
-
+
params = urllib.parse.parse_qs(queue_track.uri.split('?')[1])
track_id = params['track_id'][0]
provider = params['provider'][0]
LOGGER.info("Stream queue track: %s - %s" % (track_id, queue_track.name))
audiodata = await self.__get_raw_audio(track_id, provider, sample_rate)
+ fade_bytes = int(sample_rate * 4 * 2 * fade_length)
+ LOGGER.debug("total bytes in audio_data: %s - fade_bytes: %s" % (len(audiodata),fade_bytes))
# get fade in part
- args = 'sox -t sox - -t %s - trim 0 %s fade t %s' % (pcm_args, fade_length, fade_length)
+ args = 'sox --ignore-length -t %s - -t %s - fade t %s' % (pcm_args, pcm_args, fade_length)
process = await asyncio.create_subprocess_shell(args,
stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE)
- fade_in_part, stderr = await process.communicate(audiodata[0:15360000])
+ fade_in_part, stderr = await process.communicate(audiodata[:fade_bytes])
LOGGER.debug("Got %s bytes in memory for fadein_part after sox" % len(fade_in_part))
if last_fadeout_data:
# perform crossfade with previous fadeout samples
await sox_proc.stdin.drain()
del fade_in_part
- # get middle frames (main track without the fade-in and fade-out)
- args = 'sox -t sox - -t %s - trim %s -%s' % (pcm_args, fade_length, fade_length)
- process = await asyncio.create_subprocess_shell(args,
- stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE)
- middle_part, stderr = await process.communicate(audiodata)
- LOGGER.debug("Got %s bytes in memory for middle_part after sox" % len(middle_part))
- sox_proc.stdin.write(middle_part)
+ # feed the middle part into the main sox
+ sox_proc.stdin.write(audiodata[fade_bytes:-fade_bytes])
await sox_proc.stdin.drain()
- del middle_part
# get fade out part
- args = 'sox -t sox - -t %s - reverse trim 0 %s fade t %s reverse ' % (pcm_args, fade_length, fade_length)
+ args = 'sox --ignore-length -t %s - -t %s - reverse fade t %s reverse' % (pcm_args, pcm_args, fade_length)
process = await asyncio.create_subprocess_shell(args,
stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE)
- last_fadeout_data, stderr = await process.communicate(audiodata)
+ last_fadeout_data, stderr = await process.communicate(audiodata[-fade_bytes:])
LOGGER.debug("Got %s bytes in memory for fade_out_part after sox" % len(last_fadeout_data))
# cleanup audio data
del audiodata
+
+ # wait for the queue to consume the data
+ while buffer.qsize() > 5 and not cancelled.is_set():
+ await asyncio.sleep(1)
+ if cancelled.is_set():
+ break
+ # assume end of track and increase queue_index
+ player.cur_queue_index += 1
+ await self.mass.player.trigger_update(player_id)
# end of queue reached, pass last fadeout bits to final output
if last_fadeout_data:
''' get raw pcm data for a track upsampled to given sample_rate packed as wav '''
audiodata = b''
cachefile = self.__get_track_cache_filename(track_id, provider)
+ pcm_args = 'raw -b 32 -c 2 -e signed-integer'
if self.mass.config['base']['http_streamer']['volume_normalisation']:
gain_correct = await self.__get_track_gain_correct(track_id, provider)
else:
gain_correct = -6 # always need some headroom for upsampling and crossfades
if os.path.isfile(cachefile):
# we have a cache file for this track which we can use
- args = 'sox -t flac "%s" -t sox - vol %s dB rate -v %s' % (cachefile, gain_correct, sample_rate)
+ args = 'sox -t flac "%s" -t %s - vol %s dB rate -v %s' % (cachefile, pcm_args, gain_correct, sample_rate)
process = await asyncio.create_subprocess_shell(args, stdout=asyncio.subprocess.PIPE)
else:
# stream from provider
input_content_type = await self.mass.music.providers[provider].get_stream_content_type(track_id)
assert(input_content_type)
- args = 'sox -t %s - -t sox - vol %s dB rate -v %s' % (input_content_type, gain_correct, sample_rate)
+ args = 'sox -t %s - -t %s - vol %s dB rate -v %s' % (input_content_type, pcm_args, gain_correct, sample_rate)
process = await asyncio.create_subprocess_shell(args,
stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE)
asyncio.get_event_loop().create_task(
self.__fill_audio_buffer(process.stdin, track_id, provider, input_content_type))
#await process.wait()
audiodata, stderr = await process.communicate()
- LOGGER.debug("__get_pcm_audio for track_id %s completed" % track_id)
+ LOGGER.debug("__get_raw_audio for track_id %s completed" % (track_id))
return audiodata
async def __get_audio_stream(self, audioqueue, track_id, provider, player_id=None, cancelled=None):
process = await asyncio.create_subprocess_shell(cmd)
await process.wait()
if self.mass.config['base']['http_streamer']['enable_cache'] and not os.path.isfile(cachefile):
- # use sox to store cache file (optionally strip silence from start and end)
- if self.mass.config['base']['http_streamer']['trim_silence']:
- cmd = 'sox -t %s %s -t flac -C5 %s silence 1 0.1 1%% reverse silence 1 0.1 1%% reverse' %(content_type, tmpfile, cachefile)
- else:
- # cachefile is always stored as flac
- cmd = 'sox -t %s %s -t flac -C5 %s' %(content_type, tmpfile, cachefile)
+ # use sox to store cache file (strip silence from start and end for better transitions)
+ cmd = 'sox -t %s %s -t flac -C5 %s silence 1 0.1 1%% reverse silence 1 0.1 1%% reverse' %(content_type, tmpfile, cachefile)
process = await asyncio.create_subprocess_shell(cmd)
await process.wait()
# always clean up temp file
await buf.drain()
buf.write_eof()
fd.close()
- LOGGER.debug("fill_audio_buffer complete for track %s" % track_id)
+ LOGGER.info("fill_audio_buffer complete for track %s" % track_id)
# successfull completion, process temp file for analysis
self.mass.event_loop.create_task(
self.__analyze_audio(tmpfile, track_id, provider, content_type))