Сбой загрузки видео из Twitter на Android

#android #video #twitter

#Android #Видео #Twitter

Вопрос:

Я пытаюсь загрузить видео в Twitter на Android.

Каждый раз, когда я использую API, я получаю HTTP 400 (без сообщения об ошибке) по команде APPEND.

Мой запрос выглядит следующим образом (с использованием httpbin)

 HEADERS

User-Agent: okhttp/3.2.0
Content-Length: 4000810
Total-Route-Time: 0
Accept-Encoding: gzip
Cf-Ipcountry: JP
Connection: close
Authorization: OAuth oauth_consumer_key="[Redacted]", oauth_nonce="[Redacted]", oauth_signature="[Redacted]+kY9kybcE=", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1476240797", oauth_token="-[Redacted]", oauth_version="1.0"
Host: requestb.in
Content-Type: multipart/form-data; boundary=81302723-6d1b-4c0b-898a-ca52dd2aef10
Cf-Connecting-Ip: 118.238.220.243
X-Request-Id: e68c732e-5b59-4671-823a-f2ef1aa4e5c7
Via: 1.1 vegur
Cf-Visitor: {"scheme":"http"} => https for the real one
Connect-Time: 0
Cf-Ray: 2f0742ba47e0132f-NRT
RAW BODY

--81302723-6d1b-4c0b-898a-ca52dd2aef10
Content-Disposition: form-data; name="command"
Content-Transfer-Encoding: binary
Content-Type: application/json; charset=UTF-8
Content-Length: 8

"APPEND"
--81302723-6d1b-4c0b-898a-ca52dd2aef10
Content-Disposition: form-data; name="media_id"
Content-Transfer-Encoding: binary
Content-Type: application/json; charset=UTF-8
Content-Length: 20

"[REDACTED]"
--81302723-6d1b-4c0b-898a-ca52dd2aef10
Content-Disposition: form-data; name="media"
Content-Transfer-Encoding: binary
Content-Type: video/avc
[Redacted binary gibberish]
  

Ошибка, которую я получаю при использовании API,:

Ответ { протокол= h2, код= 400, сообщение=, url=https://upload.twitter.com/1.1/media/upload.json }

Конечный файл составляет 6 МБ; и ffmpeg -i on the file yeild the result

Метаданные: major_brand : mp42 minor_version : 0 совместимые бренды: isommp42 время создания: 2016-10-12 02:21:19 com.android.версия: 6.0 Продолжительность: 00:00:22.12, начало: 0.000000, битрейт: 2387 кб / с Поток # 0:0 (русский): Видео: h264 (ограниченный базовый уровень) (avc1 / 0x31637661), yuv420p, 480×480, 1135 кб/с, SAR 1:1 DAR 1:1, 25 кадров в секунду, 25 тбит/с, 90 Тыс. тбит/с, 50 тбит/с (по умолчанию) Метаданные: время создания: 2016-10-12 02:21:19_имя обработчика: видеохандл

Код для обработки загрузки выглядит следующим образом:

 class MPTwitterApiClient extends TwitterApiClient {
    public MPTwitterApiClient(TwitterSession session) {
        super(session);
    }

    /**
     * Provide CustomService with defined endpoints
     */
    public VideoService getVideoService() {
        return getService(VideoService.class);
    }

}

// example users/show service endpoint
interface VideoService {


    @FormUrlEncoded()
    @POST("https://upload.twitter.com/1.1/media/upload.json")
    Call<VideoUploadInit> uploadVideoInit(@Field("command") String command,
                                 @Field("total_bytes") String totalBytes,
                                 @Field("media_type") String mediaType);
    @Multipart
    @POST("https://upload.twitter.com/1.1/media/upload.json")
    Call <VideoUploadPart>uploadVideoAppend(@Part("command") String command,
                           @Part("media_id") String mediaId,
                           @Part("media") RequestBody media, // The raw binary file content being uploaded. Cannot be used with media_data.
                           // Required after an INIT, an index number starting at zero indicating the order of the uploaded chunks.
                           // The chunk of the upload for a single media, from 0-999, inclusive.
                           // The first segment_index is 0, the second segment uploaded is 1, etc.
                           @Part("segment_index") String segmentIndex);

    @POST("https://upload.twitter.com/1.1/media/upload.json")
      @FormUrlEncoded()
    Call<VideoUploadEnd>  uploadVideoFinalize(@Field("command") String command,
                             @Field("media_id") long mediaId);
    public class VideoUploadInit {

        @SerializedName("media_id")
        public final long mediaId;

        public VideoUploadInit(final long pMediaId) {
            mediaId = pMediaId;
        }

    }

    public class VideoUploadPart {

    }

    public class VideoUploadEnd {

    }
}
  

И код загрузки:

  private void uploadChunk(final VideoService videoService, final byte data[], final long mediaId , final int fileSize, final int chunkPart) {
        final int maxChunk = 4 * 1000 * 1000;
        final int byteSent = chunkPart * maxChunk;
        final boolean isLast = byteSent   maxChunk >= fileSize;
        RequestBody body = new RequestBody() {
            @Override
            public MediaType contentType() {
                return MediaType.parse("video/mp4");
            }

            @Override
            public void writeTo(final BufferedSink sink) throws IOException {
                sink.write(data, byteSent, isLast ? fileSize - byteSent : maxChunk);
            }
        };
        videoService.uploadVideoAppend("APPEND", String.valueOf(mediaId), body, String.valueOf(chunkPart)).enqueue(new Callback<VideoService.VideoUploadPart>() {
            @Override
            public void success(final Result result) {

                Log.d(TAG, "Uploaded video part");
                if (isLast) {
                    videoService.uploadVideoFinalize("FINALIZE", mediaId).enqueue(new Callback<VideoService.VideoUploadEnd>() {
                        @Override
                        public void success(final Result<VideoService.VideoUploadEnd> result) {
                            Log.e(TAG, "Finalized upload !");
                        }

                        @Override
                        public void failure(final TwitterException exception) {
                            Log.e(TAG, "Failed upload finalization");
                        }
                    });
                } else {
                    uploadChunk(videoService, data, mediaId, fileSize, chunkPart   1);
                }
            }

            @Override
            public void failure(final TwitterException exception) {
                Log.e(TAG, "Could not upload video: "   exception);
            }
        });
    }


    private void tweet(TwitterSession pTwitterSession) {
        TwitterAuthToken authToken = pTwitterSession.getAuthToken();
        String token = authToken.token;
        String secret = authToken.secret;
        MPTwitterApiClient twitterApiClient = new MPTwitterApiClient(pTwitterSession);
        final VideoService videoService = twitterApiClient.getVideoService();
        final File videoFile = new File(AssetsUtils.getExportMoviePath(getApplicationContext()));
        Log.d(TAG, "File Size is "   (videoFile.length() / 1024 / 1024)   "mb");
        try {
            final byte data[] = Files.toByteArray(videoFile);
            videoService.uploadVideoInit("INIT", String.valueOf(videoFile.length()), "video/mp4").enqueue(new Callback<VideoService.VideoUploadInit>() { //XXX refactor this callback hell
                @Override
                public void success(final Result<VideoService.VideoUploadInit> result) {
                    Log.d(TAG, "Succeed INIT RESULT: "  result);
                    Log.d(TAG, "media ID is "   result.data.mediaId);
                    final long mediaId = result.data.mediaId;
                    final int fileSize = (int)videoFile.length();
                    uploadChunk(videoService, data, mediaId, fileSize, 0);
                    }
                @Override
                public void failure(final TwitterException exception) {
                    Log.d(TAG, "Failed twitter init with "   exception);
                }
            });
        } catch (IOException pE) {
            pE.printStackTrace();
        }
    }
  

Вызов ИНИЦИАЛИЗАЦИИ носителя выполняется успешно и возвращает идентификатор носителя. Добавляемая часть завершается неудачей при первом фрагменте.

(Да, все это белое, потому что содержимое все еще находится под NDA).

Комментарии:

1. Вот коды ошибок Twitter и пояснения для разработчиков. Может быть, это вам поможет. dev.twitter.com/overview/api/response-codes а этот dev.twitter.com/ads/basics/response-codes

2. @ArsenSench да, я хотел бы использовать его, но сообщение об ошибке пустое. Обновил сообщение, чтобы отразить это.

Ответ №1:

Ладно, я наконец-то понял.

Запрос был отформатирован не так, как этого хотел Twitter. Я обновил этот код (для этого вам потребуется небольшой рефакторинг).

 private void uploadChunk(final VideoService videoService, final byte data[], final long pMediaId , final int fileSize, final int chunkPart) {
    final int maxChunk = 4 * 1000 * 1000;
    final int byteSent = chunkPart * maxChunk;
    final boolean isLast = byteSent   maxChunk >= fileSize;
    RequestBody body = new RequestBody() {
        @Override
        public MediaType contentType() {
            return MediaType.parse(Encoder.MIME_TYPE);
        }

        @Override
        public void writeTo(final BufferedSink sink) throws IOException {
            sink.write(data, byteSent, isLast ? fileSize - byteSent : maxChunk);
        }
    };
    final RequestBody mediaIdBody = new RequestBody() {
        @Override
        public MediaType contentType() {
            return MediaType.parse("text/plain");
        }

        @Override
        public void writeTo(final BufferedSink sink) throws IOException {
            sink.writeString(String.valueOf(pMediaId), Charset.defaultCharset());
        }
    };
    RequestBody appendCommand = new RequestBody() {
        @Override
        public MediaType contentType() {
            return MediaType.parse("text/plain");
        }

        @Override
        public void writeTo(final BufferedSink sink) throws IOException {
            sink.writeString("APPEND", Charset.defaultCharset());
        }
    };
    RequestBody chunkPartBody = new RequestBody() {
        @Override
        public MediaType contentType() {
            return MediaType.parse("text/plain");
        }

        @Override
        public void writeTo(final BufferedSink sink) throws IOException {
            sink.writeString(String.valueOf(chunkPart), Charset.defaultCharset());
        }
    };

    videoService.uploadVideoAppend(appendCommand, mediaIdBody, body, chunkPartBody).enqueue(new Callback<VideoService.VideoUploadPart>() {
        @Override
        public void success(final Result result) {
            Log.d(TAG, "Uploaded video part");
            if (isLast) {
                videoService.uploadVideoFinalize("FINALIZE", pMediaId).enqueue(new Callback<VideoService.VideoUploadEnd>() {
                    @Override
                    public void success(final Result<VideoService.VideoUploadEnd> result) {
                        Log.e(TAG, "Finalized upload !");
                    }

                    @Override
                    public void failure(final TwitterException exception) {
                        Log.e(TAG, "Failed upload finalization");
                    }
                });
            } else {
                uploadChunk(videoService, data, pMediaId, fileSize, chunkPart   1);
            }
        }

        @Override
        public void failure(final TwitterException exception) {
            Log.e(TAG, "Could not upload video: "   exception);
        }
    });
}
  

И в клиенте api twitter:

 @Multipart
@POST("https://upload.twitter.com/1.1/media/upload.json")
Call <VideoUploadPart>uploadVideoAppend(@Part("command") RequestBody command,
                       @Part("media_id") RequestBody mediaId,
                       @Part("media") RequestBody media, // The raw binary file content being uploaded. Cannot be used with media_data.
                       // Required after an INIT, an index number starting at zero indicating the order of the uploaded chunks.
                       // The chunk of the upload for a single media, from 0-999, inclusive.
                       // The first segment_index is 0, the second segment uploaded is 1, etc.
                       @Part("segment_index") RequestBody segmentIndex);