I haven\'t found relevant and up to date answer on SO.
Here is the code I\'m currently using, all requests are handled correctly but no video is posted ?
<
If you want to convert @Trung's code to Swift 4, and if you want to USE TwitterKit, you can use this enum to handle all upload stages:
enum UploadStage {
case initial(size: String, videoDuration: Int?) // if your video duration is <= 30s, you can pass nil here
case append(mediaId: String, videoData: Data, segment: Int)
case finalize(mediaId: String)
case status(status: String, mediaId: String)
static let videoChunkMaxSize = 5 * 1000 * 1000
var parameters: [String: Any] {
get {
switch self {
case .initial(let size, let videoDuration):
var params = ["command":stageName, "total_bytes": size, "media_type": "video/mp4"]
if let videoDuration = videoDuration, videoDuration > 30 {
params["media_category"] = "tweet_video"
}
return params
case .append(let mediaId, _ , let segment):
let videoChunkString = self.videoChunk!.base64EncodedString(options: [])
return ["command":stageName, "media_id": mediaId, "segment_index": "\(segment)", "media": videoChunkString]
case .finalize(let mediaId):
return ["command":stageName, "media_id": mediaId]
case .status(let status, let mediaId):
return ["status": status, "wrap_links": "true", "media_ids": mediaId]
}
}
}
var stageName: String {
get {
switch self {
case .initial:
return "INIT"
case .append:
return "APPEND"
case .finalize:
return "FINALIZE"
case .status:
return "STATUS"
}
}
}
var videoChunk: Data? {
switch self {
case .append(_ , let videoData, let segment):
if videoData.count > UploadStage.videoChunkMaxSize {
let maxPos = segment * UploadStage.videoChunkMaxSize + UploadStage.videoChunkMaxSize
let range: Range = segment * UploadStage.videoChunkMaxSize..<(maxPos >= videoData.count ? videoData.count : maxPos)
return videoData.subdata(in: range)
}
return videoData
default:
return nil
}
}
var urlString: String {
switch self {
case .initial, .append, .finalize:
return "https://upload.twitter.com/1.1/media/upload.json"
case .status:
return "https://api.twitter.com/1.1/statuses/update.json"
}
}
}
UploadStage enum can be used in the method with recursive calls by passing next stage enum value like this:
func uploadTwitterVideo(videoData: Data, status: String, stage: UploadStage, success: @escaping () -> Void, failure: @escaping (Error?) -> Void) {
let client = TWTRAPIClient.withCurrentUser()
var clientError: NSError?
let urlRequest = client.urlRequest(withMethod: "POST", urlString: stage.urlString, parameters: stage.parameters, error: &clientError)
if clientError == nil {
client.sendTwitterRequest(urlRequest) { (urlResponse, responseData, connectionError) in
guard connectionError == nil else {
print("There was an error: \(connectionError!.localizedDescription)")
failure(connectionError)
return
}
self.handleError(urlResponse, failure: failure)
if let data = responseData, let dataString = String(data: data, encoding: .utf8), let urlResponse = urlResponse {
print("Twitter stage \(stage.stageName) URL response : \(urlResponse), response data: \(dataString)")
var nextStage: UploadStage?
do {
switch stage {
case .initial:
let returnedJSON = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! [String:Any]
if let mediaId = returnedJSON["media_id_string"] as? String {
print("stage one success, mediaID -> \(mediaId)")
nextStage = .append(mediaId: mediaId, videoData:videoData, segment: 0)
}
case .append(let mediaId, let videoData, let segment):
if ((segment + 1) * UploadStage.videoChunkMaxSize < videoData.count) {
nextStage = .append(mediaId: mediaId, videoData: videoData, segment: segment + 1)
} else {
nextStage = .finalize(mediaId: mediaId)
}
case .finalize(let mediaId):
nextStage = .status(status: status, mediaId: mediaId)
case .status:
success()
}
if let nextStage = nextStage {
self.uploadTwitterVideo(videoData: data, status: status, stage: nextStage, success: success, failure: failure)
}
} catch let error as NSError {
failure(error)
}
}
}
}
}