2016-10-11 1146 views
0

我正嘗試在Android上將視頻上傳到Twitter。Android上的Twitter視頻上傳失敗

每次使用API​​時,都會在APPEND命令上得到一個HTTP 400(沒有錯誤消息)。

我的請求是這樣的(使用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]%2BkY9kybcE%3D", 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 }

最終文件爲6mb;和ffmpeg -i on the file yeild the result

元數據: major_brand:MP42 minor_version:0 compatible_brands:isommp42 CREATION_TIME:2016年10月12日2點21分十九秒 com.android.version:6.0時間:00:00:22.12 ,開始:0.000000,比特率:2387kb/s,流#0:0(eng):視頻:h264(約束基線)(avc1/0x31637661),yuv420p,480x480,1135kb/s,SAR1:1 DAR1: 1,25 fps,25 tbr,90k tbn,50 tbc(默認) 元數據: creation_time:2016-10-12 02:21:19 handler_name:VideoHandle

處理上傳的代碼如下所示:

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(); 
     } 
    } 

媒體INIT調用運行成功並返回一個介質ID。追加部分在第一個塊上失敗。

(是的,這是全白的,因爲內容仍然在NDA之下)。

+0

以下是Twitter的錯誤代碼和開發人員的解釋。也許它會幫助你。 https://dev.twitter.com/overview/api/response-codes 而這一次 https://dev.twitter.com/ads/basics/response-codes –

+0

@ArsenSench是的,我希望我可以使用它,但錯誤信息是空白的。更新了帖子以反映這一點。 – Antzi

回答

0

好吧,我終於想通了。

該請求未被格式化爲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); 
     } 
    }); 
} 

而在Twitter的API客戶端:

@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);