pychromecast icon indicating copy to clipboard operation
pychromecast copied to clipboard

Playback Stops in the Middle of Streaming

Open jhamilt5 opened this issue 7 years ago • 9 comments

I'm writing an application that leverages both pychromecast and gmusicapi to create a collaborative music queue to stream to my Google Home. I send streaming URLs to my Google Home like so:

from gmusicapi import Mobileclient
import pychromecast
from getpass import getpass

email = input("Email: ")
pwd = getpass()
android_id = 'XXXXXXXXXXXXXXXX' # ID of registered google play music device
api = Mobileclient()
api.login(email, pwd, android_id)    # successful login

cast = pychromecast.Chromecast('192.168.10.104')
mc = cast.media_controller

track_id = 'Tpa22h67vzxazrkpbtmuu543h24'   # The Google Play Store ID for a song
stream_url = api.get_stream_url(track_id, device_id=android_id)   # yields a url pointing to a streaming source for an mp3
mc.play_media(stream_url, 'audio/mpeg')

My Google Home chimes, letting me know that a new app has started, and shortly after, the music begins playing. However, around 75% of the way through the song, playback stops, and it cannot be restarted with successive calls to play_media() or with voice commands.

According to this issue on gmusicapi it appears to be happening because the stream_url is only valid for ~90 seconds, and the song hasn't completely buffered.

Is it possible in play_media() to force a complete load of the song instead of a streamed buffer?

jhamilt5 avatar May 26 '17 21:05 jhamilt5

Hi,

I am having the same issue as well. However, everything works fine on a chromecast.

anthonyzhub avatar May 05 '18 01:05 anthonyzhub

This just started happening to me about a week ago with untouched code that has worked for years, almost exactly as shown above except that I'm using pychromecast.get_chromecasts() instead of explicitly providing the IP. It happens 100% of the time (previously never happened).

stefan-sherwood avatar Sep 05 '19 19:09 stefan-sherwood

Same issue for me. The code was working long time but the music stops randomly. Does anyone already have a workaround or an idea how to debug this?

gallmerci avatar Oct 10 '19 19:10 gallmerci

I solved it by downloading the whole file and then playing from the local version. Because the download finishes before Google expires the link, it doesn't get cut off. A bit of a nuisance but it does work.

stefan-sherwood avatar Oct 11 '19 14:10 stefan-sherwood

Thanks. But not really a satisfying solution :(

gallmerci avatar Oct 11 '19 16:10 gallmerci

It means that my code behaves as it did before Google broke it so I'm ok with it.

stefan-sherwood avatar Oct 11 '19 17:10 stefan-sherwood

Good point. I implemented now a logic in my server application that tries to identify this situation by checking player state and idle reason. Then I just get a fresh URL and restart the song at the same position when it stopped playing. Of course, you hear the pause but it is just for 1-2 secs and so far I am quite satisfied with this workaround :)

gallmerci avatar Oct 11 '19 17:10 gallmerci

@gallmerci Great job!

@stefan-sherwood You downloaded the file? I have some songs on my computer that I use for testing, but Google Home automatically stops a song after ~10 seconds. I am aware that the link expires before the song finishes, so how were you able to overcome this problem?

anthonyzhub avatar Oct 22 '19 22:10 anthonyzhub

I'm using Python 3.7 on Windows 10. I stripped out a bunch of error handling to make it easier to read.

self.cache_and_stream_gmusic_track_on_chromecast( track_id )


# Serve locally cached songs using a subclassed BaseHTTPRequestHandler:
def do_GET( self ):
	track_id = self.get_track_id_from_url( self.path )
	file = self.get_music_fullpath( track_id )

	seek_to = 0
	if "Range" in self.headers:
		res = re.match( "(?P<units>[^=]*)=(?P<start>\\d+)-(?P<end>\\d*)", self.headers[ "Range" ] )
		if res:
			seek_to = int( res.group( "start" ) )
			self.logger.log( f"Requested byte offset: {seek_to}" )

	with open( file, 'rb' ) as f:
		if seek_to:
			print( f"Seeking to {seek_to}" )
			f.seek( seek_to )
		data = f.read()

	self.send_response( 200 )
	self.send_header( "Accept-Ranges", "bytes" )
	self.send_header( "Content-Type", "audio/mpeg" ) 
	self.send_header( "Content-Disposition", f"filename={file} )
	self.send_header( "Content-Length", f"{len( data )}" )

# Connect to Google Music API
def get_music_api( self ):
	self.music = gmusicapi.Mobileclient( debug_logging = False )
	if not music.oauth_login( device_id = music.FROM_MAC_ADDRESS ):
		self.logger.err( "GMusic login failed" )

def get_music_url( self, track_id ):
	return f"{self.server_ip}/{self.get_cache_path( track_id )}"

def get_music_fullpath( self, track_id ):
	return f"{self.library_loc}\\{self.get_cache_path( track_id )}"

# Retrieve and cache a song:
def get_cache_path( self, track_id ):
	from urllib.request import urlretrieve

	url = self.music.get_stream_url( track_id )
	path = f"library\\{store_id}"
	thing = urlretrieve( url, path )

	if thing[1].get_content_maintype() == 'audio':
		self.db.add_song_to_cache( store_id, thing[0] )
		return path
	else:
		self.logger.error( Fetch of Song ID {store_id} returned a non-audio type")

def get_stream_target( self, device_name ):
	import pychromecast
	devices = pychromecast.get_chromecasts()
	return next( (dev for dev in devices if dev.name == device_name), None )

def cache_and_stream_gmusic_track_on_chromecast( self, song_id ):
	url = self.get_music_url( song_id )
	self.streamer = self.get_stream_target( 'Whole House' )
	self.streamer.media_controller.play_media( song_url, 'audio/mpeg', metadata = song_info, current_time=offset )

stefan-sherwood avatar Oct 23 '19 15:10 stefan-sherwood