YouTube API 3 Загрузка видео – доступ не настроен – Android

Я работаю над приложением для Android, которое записывает видео и позволяет пользователю загружать его непосредственно на YouTube с помощью API данных YouTube v3.

Я настроил свое приложение в консоли Google API. В службах у меня включен YouTube Data API v3. В разделе «Доступ к API» у меня есть как раздел «Идентификатор клиента для установленных приложений» (включая идентификатор клиента и секрет клиента), так и раздел «Простой доступ к API» -> «Ключ для приложений Android (с сертификатами)» (который включает ключ API и раздел «Приложения для Android», который пока остается пустым, т.е. разрешить все приложения для Android, но я пробовал это с настройкой моего ключа Android).

Я взял свой код из нескольких мест, в первую очередь:

https://developers.google.com/youtube/v3/code_samples/java#upload_a_video

и

https://code.google.com/p/google-api-java-client/source/browse/tasks-android-sample/src/main/java/com/google/api/services/samples/tasks/android/TasksSample.java?repo=samples

Загрузка инициализируется ОК, запускает AsyncTask, но затем я получаю исключение IOException, говорящее:

{
    "code": 403,
    "errors": [
        {
            "domain": "usageLimits",
            "message": "Access Not Configured",
            "reason": "accessNotConfigured"
        }
    ],
    "message": "Access Not Configured"
}

Подобные сообщения SO предполагают, что это связано с настройками моей консоли Google API, но я не могу найти ничего плохого. Какие-либо предложения? Интересно, это потому, что я нигде не предоставляю свой идентификатор клиента или секрет ...

Спасибо.

Мой код запускается из фрагмента, содержащего список видео. Соответствующие разделы:

-- В этом

public class UploadFragment extends Fragment {

    private static GoogleAccountCredential credential;
    private static final HttpTransport transport = AndroidHttp.newCompatibleTransport();
    private static final JsonFactory jsonFactory = new GsonFactory();
    public YouTube youtube;
    List<String> scopes = Lists.newArrayList(YouTubeScopes.YOUTUBE_UPLOAD);
    private static String VIDEO_FILE_FORMAT = "video/*";

    static final int REQUEST_GOOGLE_PLAY_SERVICES = 0;
    static final int REQUEST_AUTHORIZATION = 1;
    static final int REQUEST_ACCOUNT_PICKER = 2;

-- Настройка учетных данных и YouTube

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        ...
        credential = googleAccountCredential(scopes);
        youtube = new YouTube.Builder(transport, jsonFactory, credential)
            .setApplicationName("MyAppName")
            .build();
        ...
    }

-- При нажатии кнопки инициировать загрузку

    @Override void onClick(View v) {
        ...
        if (hasGooglePlayServices()) {
            uploadYouTubeVideos();

        ...
    }

-- Создание учетных данных

    /**
     * Get the credential to authorize the installed application to access user's protected data.
     *
     * @param scopes list of scopes needed to run YouTube upload.
     */
    private static GoogleAccountCredential googleAccountCredential(List<String> scopes) throws Exception {
        credential = GoogleAccountCredential.usingOAuth2(context, scopes)
            .setSelectedAccountName(PreferenceManager.getAccountName());
        return credential;
    }

-- Запрос аккаунта у пользователя

    /**
     * Fire intent to get user to choose account
     * Return to onActivityResult
     */
    private void chooseAccount() {
        startActivityForResult(credential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
    }

-- По возвращении от выбора пользователя и учетной записи -- / запрос авторизации

    /**
     * Returns from chooseAccount and from request authorization
     */
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case REQUEST_AUTHORIZATION:
                if (resultCode == Activity.RESULT_OK) {
                    uploadYouTubeVideos();
                } else {
                    chooseAccount();
                }
                break;
            case REQUEST_ACCOUNT_PICKER:
                if (resultCode == Activity.RESULT_OK && data != null && data.getExtras() != null) {
                    String accountName = data.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME);
                    if (accountName != null) {
                        credential.setSelectedAccountName(accountName);
                        PreferenceManager.setAccountName(accountName);
                        uploadYouTubeVideos();
                    }
                }
                break;
        }
    }

-- Вызывается несколько раз в зависимости от того, какая информация у нас есть -- учетная запись, авторизация и т. д.

    /**
     * Uploads user selected video to the user's YouTube account using OAuth2
     * for authentication.
     *
     * @param videoFile file to be uploaded
     */
    public void uploadYouTubeVideos() {
        if (credential.getSelectedAccountName() == null) {
            chooseAccount();
        } else {
            File videoFile = getVideoFile();
            Insert videoInsert = prepareUpload(videoFile);
            new VideoUploadAsyncTask().execute(videoInsert);
        }
    }

-- Подготовьте загрузку -- Объедините все воедино

    /**
     * Prepare upload. Just leaves execute to be run in AsyncTask.
     *
     * @param videoFile file to be uploaded
     * @return 
     */
    public Insert prepareUpload( File videoFile ) {
        try {
            // Add extra information to the video before uploading.
            Video videoObjectDefiningMetadata = new Video();

            // Set the video to public (default).
            VideoStatus status = new VideoStatus();
            status.setPrivacyStatus("public");
            videoObjectDefiningMetadata.setStatus(status);

            // We set a majority of the metadata with the VideoSnippet object.
            VideoSnippet snippet = new VideoSnippet();

            // Video file name.
            snippet.setTitle(videoFile.getName());
            snippet.setDescription("Test description");

            // Set keywords.
            List<String> tags = new ArrayList<String>();
            tags.add("test");
            snippet.setTags(tags);

            // Set completed snippet to the video object.
            videoObjectDefiningMetadata.setSnippet(snippet);

            InputStreamContent mediaContent = new InputStreamContent(
                    VIDEO_FILE_FORMAT, new BufferedInputStream(new FileInputStream(videoFile)));
            mediaContent.setLength(videoFile.length());

            /*
             * The upload command includes: 1. Information we want returned after file is successfully
             * uploaded. 2. Metadata we want associated with the uploaded video. 3. Video file itself.
             */
            YouTube.Videos.Insert videoInsert = youtube.videos()
                    .insert("snippet,statistics,status", videoObjectDefiningMetadata, mediaContent);

            // Set the upload type and add event listener.
            MediaHttpUploader uploader = videoInsert.getMediaHttpUploader();

            /*
             * Sets whether direct media upload is enabled or disabled. True = whole media content is
             * uploaded in a single request. False (default) = resumable media upload protocol to upload
             * in data chunks.
             */
            uploader.setDirectUploadEnabled(false);

            MediaHttpUploaderProgressListener progressListener = new MediaHttpUploaderProgressListener() {
                public void progressChanged(MediaHttpUploader uploader) throws IOException {
                    switch (uploader.getUploadState()) {
                        case INITIATION_STARTED:
                            Log.d(TAG, "Upload file: Initiation Started");
                            break;
                        case INITIATION_COMPLETE:
                            Log.d(TAG, "Upload file: Initiation Completed");
                            break;
                        case MEDIA_IN_PROGRESS:
                            Log.d(TAG, "Upload file: Upload in progress");
                            Log.d(TAG, "Upload file: Upload percentage: " + uploader.getProgress());
                            break;
                        case MEDIA_COMPLETE:
                            Log.d(TAG, "Upload file: Upload Completed!");
                            break;
                        case NOT_STARTED:
                            Log.d(TAG, "Upload file: Upload Not Started!");
                            break;
                    }
                }
            };
            uploader.setProgressListener(progressListener);

            return videoInsert;
        } catch (FileNotFoundException e) {
            Log.e(TAG, "File not found: " + e.getMessage());
            return null;
        } catch (IOException e) {
            Log.e(TAG, "IOException: " + e.getMessage());
            return null;
        }
    }

-- Требуются сервисы Google Play

    /** 
     * Pop up dialog requesting user to download Google Play Services.
     * Returns to onActivityResult
     */
    void showGooglePlayServicesAvailabilityErrorDialog(final int connectionStatusCode) {
        getActivity().runOnUiThread(new Runnable() {
            public void run() {
                Dialog dialog = 
                        GooglePlayServicesUtil.getErrorDialog(connectionStatusCode, getActivity(),
                        REQUEST_GOOGLE_PLAY_SERVICES);
                dialog.show();
            }
        });
    }

-- AsyncTask, который запускается при загрузке

    public class VideoUploadAsyncTask extends AsyncTask<Insert, Void, Void> {
        @Override
        protected Void doInBackground( Insert... inserts ) {
            Insert videoInsert = inserts[0];
            try {
                Video returnVideo = videoInsert.execute();
            } catch (final GooglePlayServicesAvailabilityIOException availabilityException) {
                showGooglePlayServicesAvailabilityErrorDialog(
                        availabilityException.getConnectionStatusCode());
            } catch (UserRecoverableAuthIOException userRecoverableException) {
                startActivityForResult(
                        userRecoverableException.getIntent(), UploadFragment.REQUEST_AUTHORIZATION);
            } catch (IOException e) {
                Log.e(TAG, "IOException: " + e.getMessage());
            }
            return null;
        }
    }

}

person Jon G    schedule 02.08.2013    source источник
comment
Связанные вопросы, заданные SO, указали мне на github.com/youtube/ytd-android, я мы рассмотрим его более подробно, но на первый взгляд кажется, что мы используем очень похожий метод.   -  person Jon G    schedule 02.08.2013
comment
Привет, можешь опубликовать весь код для справки?   -  person Mehul Joisar    schedule 03.01.2014
comment
Меня смущает ключ API Android, написанный на домашней странице проекта. Имеется ли в виду Client ID для приложения Android?   -  person Romulus Urakagi Ts'ai    schedule 23.09.2014
comment
@JonG, если удача загрузится, это сработало для вас.   -  person livemaker    schedule 24.01.2019


Ответы (2)


Ответ, предоставленный @Ibrahim, был для меня почти правильным. Что мне нужно было сделать, так это отредактировать конфигурацию моего API. Однако мне нужно было редактировать не раздел «Простой доступ к API», а настройки после нажатия кнопки «Создать другой идентификатор клиента».

Затем я мог выбрать «Установленное приложение» -> «Android». После ввода имени моего пакета и SHA1 и ожидания 15 минут мое приложение заработало, как и ожидалось. У меня также настроен «Простой доступ к API». Я не уверен, нужны ли вам оба или нет.

person Jon G    schedule 05.08.2013
comment
Привет, Джон Джи, я не понял, не могли бы вы четко объяснить, что вы это сделали. потому что у меня те же проблемы с аутентификацией в моем приложении. Заранее спасибо - person siva; 05.04.2017

Да, YouTube Direct Lite для Android похож. Вам необходимо настроить простой доступ к API с помощью ключа SHA1. Объясняет здесь.

person Ibrahim Ulukaya    schedule 02.08.2013
comment
Спасибо за ваш ответ @Ibrahim. Я попробовал это и перепроверил сейчас... похоже, это не имеет значения. Я создал ключ SHA1 и добавил его в свою консоль API как: key;com.bla.bla.myapp - person Jon G; 02.08.2013
comment
Все еще не повезло @Ibrahim. Я заметил, что нигде в моем коде я не использую свой ключ API... В предыдущей попытке я использовал YouTubeRequestInitializer вместо GoogleAccountCredential для инициализации моего объекта YouTube, который принимает ключ API в качестве входных данных. Должен ли я включать ключ API где-то в GoogleAccountCredential? - person Jon G; 03.08.2013
comment
Нет, простой доступ использует вместо этого ваш SHA1. - person Ibrahim Ulukaya; 03.08.2013
comment
Должно быть, запрос, отправляемый на сервер, не направлен на правильную учетную запись API (т. е. из-за неправильного ключа SHA1/API) или что то, что я вижу в консоли API, не соответствует тому, что видит сервер (т. е. API данных YouTube фактически не разрешен для моей учетной записи). Есть ли способ зарегистрировать точный запрос, отправляемый на сервер? - person Jon G; 05.08.2013