Chromecast - SIGN_IN_REQUIRED

时间:2014-03-28 04:44:21

标签: android google-play-services chromecast

我的用户中只有一小部分人收到了这个错误,我无法终身解决这个错误。我使用GooglePlayServicesUtil.isGooglePlayServicesAvailable(downloadService)来测试Play服务是否可用,并始终返回SUCCESS。我设置了频道以连接到Chromecast,一切正常,直到我尝试使用RemoteMediaPlayer.load。某些用户的结果始终为SIGN_IN_REQUIREDresolution: nullstatus.toString()Failed to load: Status{statusCode=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: Status{statusCode=SIGN_IN_REQUIRED, resolution=null}

2 个答案:

答案 0 :(得分:0)

很奇怪你当时正在获得这样的状态代码。我想到的是用户可能没有登录他/她的Gmail帐户或类似的东西。您是否有我们的日志文件来查看我们是否可以从上下文中获取更多信息?此外,可以肯定的是,这样的用户会看到在电视上启动的应用程序,并且只有在加载媒体时才会出现错误?

答案 1 :(得分:0)

问题是由于使用自签名证书。我没有在我的旧手机上发现这个问题,因为我已经更换了主机并在切换手机后购买了正常的证书。如果SDK通过一个有用的错误会很好。抛出的那个让你认为连接到Play Services SDK是一个问题,而不是使用实际URL的问题。