Chromecast - SIGN_IN_REQUIRED
Posted
技术标签:
【中文标题】Chromecast - SIGN_IN_REQUIRED【英文标题】: 【发布时间】:2014-03-28 04:44:20 【问题描述】:我的用户中只有一小部分遇到此错误,我终其一生都无法弄清楚。我使用GooglePlayServicesUtil.isGooglePlayServicesAvailable(downloadService)
来测试Play Services 是否可用,它总是返回SUCCESS
。我设置了连接到 Chromecast 的通道,一切正常,直到我尝试使用 RemoteMediaPlayer.load
。对于某些用户,结果总是SIGN_IN_REQUIRED
,resolution: null
。 status.toString()
是 Failed to load: StatusstatusCode=SIGN_IN_REQUIRED, resolution=null
。我真的不确定我应该怎么做,或者如何为我的少数用户解决这个错误。
我不知道什么部分是相关的,所以我只是发布我的整个控制器类:
public class ChromeCastController extends RemoteController
private static final String TAG = ChromeCastController.class.getSimpleName();
private CastDevice castDevice;
private GoogleApiClient apiClient;
private ConnectionCallbacks connectionCallbacks;
private ConnectionFailedListener connectionFailedListener;
private Cast.Listener castClientListener;
private boolean applicationStarted = false;
private boolean waitingForReconnect = false;
private boolean error = false;
private boolean ignoreNextPaused = false;
private String sessionId;
private FileProxy proxy;
private String rootLocation;
private RemoteMediaPlayer mediaPlayer;
private double gain = 0.5;
public ChromeCastController(DownloadService downloadService, CastDevice castDevice)
this.downloadService = downloadService;
this.castDevice = castDevice;
SharedPreferences prefs = Util.getPreferences(downloadService);
rootLocation = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null);
@Override
public void create(boolean playing, int seconds)
downloadService.setPlayerState(PlayerState.PREPARING);
connectionCallbacks = new ConnectionCallbacks(playing, seconds);
connectionFailedListener = new ConnectionFailedListener();
castClientListener = new Cast.Listener()
@Override
public void onApplicationStatusChanged()
if (apiClient != null && apiClient.isConnected())
Log.i(TAG, "onApplicationStatusChanged: " + Cast.CastApi.getApplicationStatus(apiClient));
@Override
public void onVolumeChanged()
if (apiClient != null && applicationStarted)
try
gain = Cast.CastApi.getVolume(apiClient);
catch(Exception e)
Log.w(TAG, "Failed to get volume");
@Override
public void onApplicationDisconnected(int errorCode)
shutdownInternal();
;
Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions.builder(castDevice, castClientListener);
apiClient = new GoogleApiClient.Builder(downloadService)
.addApi(Cast.API, apiOptionsBuilder.build())
.addConnectionCallbacks(connectionCallbacks)
.addOnConnectionFailedListener(connectionFailedListener)
.build();
apiClient.connect();
@Override
public void start()
if(error)
error = false;
Log.w(TAG, "Attempting to restart song");
startSong(downloadService.getCurrentPlaying(), true, 0);
return;
try
mediaPlayer.play(apiClient);
catch(Exception e)
Log.e(TAG, "Failed to start");
@Override
public void stop()
try
mediaPlayer.pause(apiClient);
catch(Exception e)
Log.e(TAG, "Failed to pause");
@Override
public void shutdown()
try
if(mediaPlayer != null && !error)
mediaPlayer.stop(apiClient);
catch(Exception e)
Log.e(TAG, "Failed to stop mediaPlayer", e);
try
if(apiClient != null)
Cast.CastApi.stopApplication(apiClient);
Cast.CastApi.removeMessageReceivedCallbacks(apiClient, mediaPlayer.getNamespace());
mediaPlayer = null;
applicationStarted = false;
catch(Exception e)
Log.e(TAG, "Failed to shutdown application", e);
if(apiClient != null && apiClient.isConnected())
apiClient.disconnect();
apiClient = null;
if(proxy != null)
proxy.stop();
proxy = null;
private void shutdownInternal()
// This will call this.shutdown() indirectly
downloadService.setRemoteEnabled(RemoteControlState.LOCAL, null);
@Override
public void updatePlaylist()
if(downloadService.getCurrentPlaying() == null)
startSong(null, false, 0);
@Override
public void changePosition(int seconds)
try
mediaPlayer.seek(apiClient, seconds * 1000L);
catch(Exception e)
Log.e(TAG, "FAiled to seek to " + seconds);
@Override
public void changeTrack(int index, DownloadFile song)
startSong(song, true, 0);
@Override
public void setVolume(boolean up)
double delta = up ? 0.1 : -0.1;
gain += delta;
gain = Math.max(gain, 0.0);
gain = Math.min(gain, 1.0);
getVolumeToast().setVolume((float) gain);
try
Cast.CastApi.setVolume(apiClient, gain);
catch(Exception e)
Log.e(TAG, "Failed to the volume");
@Override
public int getRemotePosition()
if(mediaPlayer != null)
return (int) (mediaPlayer.getApproximateStreamPosition() / 1000L);
else
return 0;
@Override
public int getRemoteDuration()
if(mediaPlayer != null)
return (int) (mediaPlayer.getStreamDuration() / 1000L);
else
return 0;
void startSong(DownloadFile currentPlaying, boolean autoStart, int position)
if(currentPlaying == null)
try
if (mediaPlayer != null && !error)
mediaPlayer.stop(apiClient);
catch(Exception e)
// Just means it didn't need to be stopped
downloadService.setPlayerState(PlayerState.IDLE);
return;
downloadService.setPlayerState(PlayerState.PREPARING);
MusicDirectory.Entry song = currentPlaying.getSong();
try
MusicService musicService = MusicServiceFactory.getMusicService(downloadService);
String url;
// Offline, use file proxy
if(Util.isOffline(downloadService) || song.getId().indexOf(rootLocation) != -1)
if(proxy == null)
proxy = new FileProxy(downloadService);
proxy.start();
url = proxy.getPublicAddress(song.getId());
else
if(proxy != null)
proxy.stop();
proxy = null;
if(song.isVideo())
url = musicService.getHlsUrl(song.getId(), currentPlaying.getBitRate(), downloadService);
else
url = musicService.getMusicUrl(downloadService, song, currentPlaying.getBitRate());
url = fixURLs(url);
// Setup song/video information
MediaMetadata meta = new MediaMetadata(song.isVideo() ? MediaMetadata.MEDIA_TYPE_MOVIE : MediaMetadata.MEDIA_TYPE_MUSIC_TRACK);
meta.putString(MediaMetadata.KEY_TITLE, song.getTitle());
if(song.getTrack() != null)
meta.putInt(MediaMetadata.KEY_TRACK_NUMBER, song.getTrack());
if(!song.isVideo())
meta.putString(MediaMetadata.KEY_ARTIST, song.getArtist());
meta.putString(MediaMetadata.KEY_ALBUM_ARTIST, song.getArtist());
meta.putString(MediaMetadata.KEY_ALBUM_TITLE, song.getAlbum());
String coverArt = "";
if(proxy == null)
coverArt = musicService.getCoverArtUrl(downloadService, song);
coverArt = fixURLs(coverArt);
meta.addImage(new WebImage(Uri.parse(coverArt)));
else
File coverArtFile = FileUtil.getAlbumArtFile(downloadService, song);
if(coverArtFile != null && coverArtFile.exists())
coverArt = proxy.getPublicAddress(coverArtFile.getPath());
meta.addImage(new WebImage(Uri.parse(coverArt)));
String contentType;
if(song.isVideo())
contentType = "application/x-mpegURL";
else if(song.getTranscodedContentType() != null)
contentType = song.getTranscodedContentType();
else if(song.getContentType() != null)
contentType = song.getContentType();
else
contentType = "audio/mpeg";
// Load it into a MediaInfo wrapper
MediaInfo mediaInfo = new MediaInfo.Builder(url)
.setContentType(contentType)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setMetadata(meta)
.build();
if(autoStart)
ignoreNextPaused = true;
mediaPlayer.load(apiClient, mediaInfo, autoStart, position * 1000L).setResultCallback(new ResultCallback<RemoteMediaPlayer.MediaChannelResult>()
@Override
public void onResult(RemoteMediaPlayer.MediaChannelResult result)
if (result.getStatus().isSuccess())
// Handled in other handler
else if(result.getStatus().getStatusCode() != ConnectionResult.SIGN_IN_REQUIRED)
Log.e(TAG, "Failed to load: " + result.getStatus().toString());
failedLoad();
);
catch (IllegalStateException e)
Log.e(TAG, "Problem occurred with media during loading", e);
failedLoad();
catch (Exception e)
Log.e(TAG, "Problem opening media during loading", e);
failedLoad();
private String fixURLs(String url)
// Only change to internal when using https
if(url.indexOf("https") != -1)
SharedPreferences prefs = Util.getPreferences(downloadService);
int instance = prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
String externalUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null);
String internalUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_INTERNAL_URL + instance, null);
url = url.replace(internalUrl, externalUrl);
// Use separate profile for Chromecast so users can do ogg on phone, mp3 for CC
return url.replace(Constants.REST_CLIENT_ID, Constants.CHROMECAST_CLIENT_ID);
private void failedLoad()
Util.toast(downloadService, downloadService.getResources().getString(R.string.download_failed_to_load));
downloadService.setPlayerState(PlayerState.STOPPED);
error = true;
private class ConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks
private boolean isPlaying;
private int position;
private ResultCallback<Cast.ApplicationConnectionResult> resultCallback;
ConnectionCallbacks(boolean isPlaying, int position)
this.isPlaying = isPlaying;
this.position = position;
resultCallback = new ResultCallback<Cast.ApplicationConnectionResult>()
@Override
public void onResult(Cast.ApplicationConnectionResult result)
Status status = result.getStatus();
if (status.isSuccess())
ApplicationMetadata applicationMetadata = result.getApplicationMetadata();
sessionId = result.getSessionId();
String applicationStatus = result.getApplicationStatus();
boolean wasLaunched = result.getWasLaunched();
applicationStarted = true;
setupChannel();
else
shutdownInternal();
;
@Override
public void onConnected(Bundle connectionHint)
if (waitingForReconnect)
Log.i(TAG, "Reconnecting");
reconnectApplication();
else
launchApplication();
@Override
public void onConnectionSuspended(int cause)
Log.w(TAG, "Connection suspended");
isPlaying = downloadService.getPlayerState() == PlayerState.STARTED;
position = getRemotePosition();
waitingForReconnect = true;
void launchApplication()
try
Cast.CastApi.launchApplication(apiClient, CastCompat.APPLICATION_ID, false).setResultCallback(resultCallback);
catch (Exception e)
Log.e(TAG, "Failed to launch application", e);
void reconnectApplication()
try
Cast.CastApi.joinApplication(apiClient, CastCompat.APPLICATION_ID, sessionId).setResultCallback(resultCallback);
catch (Exception e)
Log.e(TAG, "Failed to reconnect application", e);
void setupChannel()
if(!waitingForReconnect)
mediaPlayer = new RemoteMediaPlayer();
mediaPlayer.setOnStatusUpdatedListener(new RemoteMediaPlayer.OnStatusUpdatedListener()
@Override
public void onStatusUpdated()
MediaStatus mediaStatus = mediaPlayer.getMediaStatus();
if (mediaStatus == null)
return;
switch (mediaStatus.getPlayerState())
case MediaStatus.PLAYER_STATE_PLAYING:
if (ignoreNextPaused)
ignoreNextPaused = false;
downloadService.setPlayerState(PlayerState.STARTED);
break;
case MediaStatus.PLAYER_STATE_PAUSED:
if (!ignoreNextPaused)
downloadService.setPlayerState(PlayerState.PAUSED);
break;
case MediaStatus.PLAYER_STATE_BUFFERING:
downloadService.setPlayerState(PlayerState.PREPARING);
break;
case MediaStatus.PLAYER_STATE_IDLE:
if (mediaStatus.getIdleReason() == MediaStatus.IDLE_REASON_FINISHED)
downloadService.setPlayerState(PlayerState.COMPLETED);
downloadService.onSongCompleted();
else if (mediaStatus.getIdleReason() == MediaStatus.IDLE_REASON_INTERRUPTED)
if (downloadService.getPlayerState() != PlayerState.PREPARING)
downloadService.setPlayerState(PlayerState.PREPARING);
else if (mediaStatus.getIdleReason() == MediaStatus.IDLE_REASON_ERROR)
Log.e(TAG, "Idle due to unknown error");
downloadService.setPlayerState(PlayerState.COMPLETED);
downloadService.next();
else
Log.w(TAG, "Idle reason: " + mediaStatus.getIdleReason());
downloadService.setPlayerState(PlayerState.IDLE);
break;
);
try
Cast.CastApi.setMessageReceivedCallbacks(apiClient, mediaPlayer.getNamespace(), mediaPlayer);
catch (IOException e)
Log.e(TAG, "Exception while creating channel", e);
if(!waitingForReconnect)
DownloadFile currentPlaying = downloadService.getCurrentPlaying();
startSong(currentPlaying, isPlaying, position);
if(waitingForReconnect)
waitingForReconnect = false;
private class ConnectionFailedListener implements GoogleApiClient.OnConnectionFailedListener
@Override
public void onConnectionFailed(ConnectionResult result)
shutdownInternal();
编辑日志:
03-28 19:04:49.757 6305-6305/github.daneren2005.dsub I/ChromeCastController﹕ onApplicationStatusChanged: Chromecast Home Screen
03-28 19:04:52.280 6305-6305/github.daneren2005.dsub I/ChromeCastController﹕ onApplicationStatusChanged: null
03-28 19:04:54.162 6305-6305/github.daneren2005.dsub I/ChromeCastController﹕ onApplicationStatusChanged: Ready To Cast
03-28 19:05:05.194 6305-6305/github.daneren2005.dsub E/ChromeCastController﹕ Failed to load: StatusstatusCode=SIGN_IN_REQUIRED, resolution=null
【问题讨论】:
【参考方案1】:奇怪的是你当时得到这样的状态码。想到的是用户可能没有登录他/她的 gmail 帐户或类似的东西。您是否有日志文件供我们查看,看看我们是否可以从上下文中获得更多信息?此外,可以肯定的是,此类用户会看到在电视上启动的应用程序,并且只有在加载媒体时才会引发错误?
【讨论】:
添加了日志。是的,它可以很好地显示启动画面,它只是拒绝实际加载任何内容。我有一台可以重现该问题的设备,它肯定已登录到 Gmail,除了没有 SIM 卡外,它似乎一切正常。 我们需要更多的日志,可能需要整个会话来查看正在发生的事情,并为 Play 服务打开调试日志。由于日志可能很大,请随时打开一个问题并将其附加到那里。 我在gist.github.com/daneren2005/c1cd23a8107f3730290c 附加了完整的日志。我看不出那里有什么特别的事情。我尝试使用 .setVerboseLoggingEnabled(true) 打开调试,但我不知道它实际上改变了什么。我有什么特别需要做的吗?如果您希望在 Chromecast 问题中继续讨论,我可以。 所以你可以自己重现这个?如果是这样,您能否尝试运行我们的一个 android 示例,看看它是否适合您。 我无法构建示例,因为无论我做什么,Gradle 都会出错。【参考方案2】:问题是由于使用了自签名证书。我没有意识到我的旧手机上的问题,因为我换了主机并在换手机后购买了普通证书。如果 SDK 能通过一个有用的错误,那就太好了。抛出的问题让您认为这是连接到 Play Services SDK 的问题,而不是实际使用的 URL 的问题。
【讨论】:
你指的是什么证书? 我的应用程序允许人们使用自己的 Subsonic 服务器,可以是 http 或 https。使用 http 服务器或带有有效证书的 https 的人可以正常使用 Chromecast。每个使用自签名证书设置服务器的人都会遇到该错误。以上是关于Chromecast - SIGN_IN_REQUIRED的主要内容,如果未能解决你的问题,请参考以下文章