iOS Swift: How to post a video to twitter with Twi

2019-01-26 21:53发布

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 let userID = Twitter.sharedInstance().sessionStore.session()?.userID {
    var client = TWTRAPIClient(userID: userID)

    let text: String = "Testing Video"
    let videoLength: String = "\(self.video.length)"
    print(videoLength)
    var initError: NSError?
    var message = ["status": text, "command" : "INIT", "media_type" : "video/m4v", "total_bytes" : videoLength]
    let preparedRequest: NSURLRequest = client.URLRequestWithMethod("POST", URL: self.strUploadUrl, parameters: message, error: &initError)
    client.sendTwitterRequest(preparedRequest, completion: { (urlResponse: NSURLResponse?, responseData: NSData?, error: NSError?) -> Void in
        if error == nil {

            do {


                let json: NSDictionary = try (NSJSONSerialization.JSONObjectWithData(responseData!, options: NSJSONReadingOptions(rawValue: 0)) as? NSDictionary)!
                print("JSON is \(json)")


                let mediaID = json.objectForKey("media_id_string") as! String

                client = TWTRAPIClient(userID: userID)
                var uploadError: NSError?
                let videoString = self.video.base64EncodedStringWithOptions([])

                message = ["command" : "APPEND", "media_id" : mediaID, "segment_index" : "0", "media" : videoString]
                let preparedRequest = client.URLRequestWithMethod("POST", URL: self.strUploadUrl, parameters: message, error: &uploadError)
                client.sendTwitterRequest(preparedRequest, completion: { (urlResponse: NSURLResponse?, responseData: NSData?, error: NSError?) -> Void in
                    if error == nil {
                        client = TWTRAPIClient(userID: userID)
                        var finalizeError: NSError?
                        message = ["command":"FINALIZE", "media_id": mediaID]
                        let preparedRequest = client.URLRequestWithMethod("POST", URL: self.strUploadUrl, parameters: message, error: &finalizeError)
                        client.sendTwitterRequest(preparedRequest, completion: { (urlResponse: NSURLResponse?, responseData: NSData?, error: NSError?) -> Void in
                            if error == nil {
                                client = TWTRAPIClient(userID: userID)
                                var sendError: NSError?
                                let message = ["status": text, "wrap_links": "true", "media_ids": mediaID]
                                //var updateMessage = NSMutableDictionary(dictionary: message)
                                let preparedRequest = client.URLRequestWithMethod("POST", URL: self.strStatusUrl, parameters: message , error: &sendError)
                                client.sendTwitterRequest(preparedRequest, completion: { (urlResponse: NSURLResponse?, responseData: NSData?, error: NSError?) -> Void in

                                })
                            } else {
                                print("Command FINALIZE failed \n \(error!)")
                            }
                        })
                    } else {
                        print("Command APPEND failed")
                    }
                })
            }

            catch {
                print("\(error)")
            }
        }

        else {
            print("\(error.debugDescription)Command INIT failed")
        }
    })
    }

All the code above is working, except that the video is not uploaded. I can't figure out what I'm missing and Twitter's documentation is very poor on posting video.

2条回答
女痞
2楼-- · 2019-01-26 22:33

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<Data.Index> = segment * UploadStage.videoChunkMaxSize..<(maxPos >= videoData.count ? videoData.count : maxPos)
                return videoData.subdata(in: range)

            }
            return videoData
        default:
            return nil
        }
    }

    var url: URL {
        switch self {
        case .initial, .append, .finalize:
            return URL(string: "https://upload.twitter.com/1.1/media/upload.json")!
        case .status:
            return URL(string: "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)
                }
            }
        }
    }
}
查看更多
Viruses.
3楼-- · 2019-01-26 22:33

I sympathize with you about about Twitter's poor documentation. Find out what error you get.

Below are implementation notes that I hope it helps:

  1. Twitter video requirement: https://dev.twitter.com/rest/public/uploading-media#videorecs.
  2. FINALIZE command verifies video file per Twitter video requirement before completing the upload.
  3. If you get "HTTP status 400 bad request" with response data having "Invalid or unsupported media, Reason: UnsupportedMedia." error after sending FINALIZE command, you need to verify your video file with Twitter video requirement.

Check out my project https://github.com/mtrung/TwitterVideoUpload. I know it's in Obj-C but it works.

查看更多
登录 后发表回答