iOS Swift: How to post a video to twitter with TwitterKit?

前端 未结 2 1530
不思量自难忘°
不思量自难忘° 2020-12-10 22:24

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 ?

<         


        
相关标签:
2条回答
  • 2020-12-10 23:11

    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.

    0 讨论(0)
  • 2020-12-10 23:19

    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 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)
                    }
                }
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题