This library is under active development. All comments, questions, pull requests, and issues are welcome. See below for details.
- Getting Started
- [Installation] (#installation)
- Prerequisites
- Which example project should I use?
- Setup your Submodules
- Initialization
- Uploading Videos
- Design Considerations
- Custom Workflows 🍪🎉
- Want to Contribute?
- Found an Issue?
- Troubleshooting
- Questions
- License
VimeoUpload is available through Carthage and coming soon CocoaPods
To install it, simply add the following line to your Podfile:
pod "VimeoUpload"
Add the following to your Cartfile:
github "vimeo/VimeoUpload"
- Ensure that you've verified your Vimeo account. When you create an account, you'll receive an email asking that you verify your account. Until you verify your account you will not be able to upload videos using the API.
- Ensure you have been granted permission to use the "upload" scope. This permission must explicitly be granted by Vimeo API admins. You can request this permission on your "My Apps" page under "Request upload access". Visit developer.vimeo.com.
- Ensure that the OAuth token that you're using to make your requests has the "upload" scope included.
The publicly available server-side Vimeo upload API is comprised of 4 separate requests that must be made in sequence. The 4 requests are:
- Create a video object
- Upload the video file
- Activate the video object
- Optionally set the video object's metadata (e.g. title, description, privacy, etc.)
A simplified server-side flow that eliminates steps 3 and 4 is being used in the current Vimeo iOS and Vimeo Android mobile apps. While it's currently for internal use only, we're actively working on making it available to the public before the end of 2017.
While VimeoUpload contains support for both of these flows, the public only has access to the 4-step upload process. That means the following:
-
If you would like to get started using an example project, you must set your build target to be
VimeoUpload-iOS-OldUpload
. This is the example that uses the publicly accessible 4-step flow. -
In order to run this project, you will have to insert a valid OAuth token into the
init
method of the class calledOldVimeoUploader
where it says"YOUR_OAUTH_TOKEN"
. You can obtain an OAuth token by visiting developer.vimeo.com and creating a new "app" and associated OAuth token. Without a valid OAuth token, you will be presented with a "Request failed: unauthorized (401)" error alert when you try to upload a video.
If you are adding this library to your own project, follow the steps outlined in Getting Started with Submodules as Development Pods.
Create an instance of VimeoUpload
, or modify VimeoUpload
to act as a singleton:
let backgroundSessionIdentifier = "YOUR_BACKGROUND_SESSION_ID"
let authToken = "YOUR_OAUTH_TOKEN"
let vimeoUpload = VimeoUpload<OldUploadDescriptor>(backgroundSessionIdentifier: backgroundSessionIdentifier, authToken: authToken)
If your OAuth token can change during the course of a session, use the constructor whose second argument is an authTokenBlock
:
let backgroundSessionIdentifier = "YOUR_BACKGROUND_SESSION_ID"
var authToken = "YOUR_OAUTH_TOKEN"
let vimeoUpload = VimeoUpload<OldUploadDescriptor>(backgroundSessionIdentifier: backgroundSessionIdentifier, authTokenBlock: { () -> String? in
return authToken
})
You can obtain an OAuth token by using the authentication methods provided by VIMNetworking or by visiting developer.vimeo.com and creating a new "app" and associated OAuth token.
In order to start an upload, you need a file URL pointing to the video file on disk that you would like to upload.
The steps required to obtain the file URL will vary depending on whether you are uploading a PHAsset or an asset that you manage outside of the device Photos environment. Once you have a valid file URL, you will use it to start your upload.
Unfortunately, because of how Apple's PHAsset APIs are designed, uploading directly from a PHAsset resource URL is not possible. In order to upload PHAssets you will need to first create a copy of the asset itself and upload from that copy. See below for instructions on how to do this.
Use VimeoUpload's PHAssetExportSessionOperation
to request an instance of AVAssetExportSession configured for the PHAsset that you intend to upload. If the PHAsset is in iCloud (i.e. not resident on the device) this will download the PHAsset from iCloud.
let phAsset = ... // The PHAsset you intend to upload
let operation = PHAssetExportSessionOperation(phAsset: phAsset)
// Optionally set a progress block
operation.progressBlock = { (progress: Double) -> Void in
// Do something with progress
}
operation.completionBlock = {
guard operation.cancelled == false else
{
return
}
if let error = operation.error
{
// Do something with the error
}
else if let exportSession = operation.result
{
// Use the export session to export a copy of the asset (see below)
}
else
{
assertionFailure("error and exportSession are mutually exclusive. This should never happen.")
}
}
operation.start()
Next, use VimeoUpload's ExportOperation
to export a copy of the PHAsset. You can then use the resulting url
to start your upload.
let exportSession = ... // The export session you just generated (see above)
let operation = ExportOperation(exportSession: exportSession)
// Optionally set a progress block
operation.progressBlock = { (progress: Double) -> Void in
// Do something with progress
}
operation.completionBlock = {
guard operation.cancelled == false else
{
return
}
if let error = operation.error
{
// Do something with the error
}
else if let url = operation.outputURL
{
// Use the url to start your upload (see below)
}
else
{
assertionFailure("error and outputURL are mutually exclusive, this should never happen.")
}
}
operation.start()
This is quite a bit simpler:
let path = "PATH_TO_VIDEO_FILE_ON_DISK"
let url = NSURL.fileURLWithPath(path)
Use the url
to start your upload:
let vimeoUpload = ... // Your instance of VimeoUpload (see above)
let url = ... // Your url (see above)
let descriptor = OldUploadDescriptor(url: url)
vimeoUpload.uploadVideo(descriptor: descriptor)
You can also pass in a VideoSettings object if you'd like. This will set your video's metadata after the upload completes:
let vimeoUpload = ... // Your instance of VimeoUpload (see above)
let url = ... // Your url (see above)
let title = "Untitled"
let description = "A really cool video"
let privacy = "nobody"
let videoSettings = VideoSettings(title: title, description: description, privacy: privacy, users: nil, password: nil)
let descriptor = OldUploadDescriptor(url: url, videoSettings: videoSettings)
vimeoUpload.uploadVideo(descriptor: descriptor)
You can use the descriptor you create to inspect state and progress, or to cancel the upload.
You can examine upload state and progress by inspecting the stateObservable
and progressObservable
properties of an OldUploadDescriptor
.
You can obtain a reference to a specific OldUploadDescriptor
by holding onto the OldUploadDescriptor
that you used to initiate the upload, or by asking VimeoUpload
for a specific OldUploadDescriptor
like so:
let identifier = ... // The identifier you set on the descriptor when you created it (see above)
let vimeoUpload = ... // Your instance of VimeoUpload (see above)
let descriptor = vimeoUpload.descriptorForIdentifier(identifier: identifier)
You can also ask VimeoUpload
for an OldUploadDescriptor
that passes a test that you construct. You can construct any test you'd like. In the case where we want a descriptor with a certain identifier, the convenience method descriptorForIdentifier
leverages descriptorPassingTest
under the hood:
let phAsset = ... // The PHAsset whose upload you'd like to inspect
let vimeoUpload = ... // Your instance of VimeoUpload (see above)
let descriptor = vimeoUpload.descriptorManager.descriptorPassingTest({ (descriptor) -> Bool in
return descriptor.identifier == phAsset.localIdentifier
})
Once you have a reference to the OldUploadDescriptor
you're interested in you can inspect its state directly:
print(descriptor.state)
print(descriptor.error?.localizedDescription)
Or use KVO to observe changes to its state and progress:
private static let ProgressKeyPath = "progressObservable"
private static let StateKeyPath = "stateObservable"
private var progressKVOContext = UInt8()
private var stateKVOContext = UInt8()
...
descriptor.addObserver(self, forKeyPath: self.dynamicType.StateKeyPath, options: .New, context: &self.stateKVOContext)
descriptor.addObserver(self, forKeyPath: self.dynamicType.ProgressKeyPath, options: .New, context: &self.progressKVOContext)
...
descriptor.removeObserver(self, forKeyPath: self.dynamicType.StateKeyPath, context: &self.stateKVOContext)
descriptor.removeObserver(self, forKeyPath: self.dynamicType.ProgressKeyPath, context: &self.progressKVOContext)
...
// MARK: KVO
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>)
{
if let keyPath = keyPath
{
switch (keyPath, context)
{
case(self.dynamicType.ProgressKeyPath, &self.progressKVOContext):
let progress = change?[NSKeyValueChangeNewKey]?.doubleValue ?? 0
// Do something with progress
case(self.dynamicType.StateKeyPath, &self.stateKVOContext):
let stateRaw = (change?[NSKeyValueChangeNewKey] as? String) ?? DescriptorState.Ready.rawValue;
let state = DescriptorState(rawValue: stateRaw)!
// Do something with state
default:
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
else
{
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
Check out the DescriptorKVObserver
class. It's a small utility that makes KVO'ing a Descriptor's state
and progress
properties easier.
Canceling an upload will cancel the file upload itself as well as delete the video object from Vimeo servers. You can cancel an upload using the OldUploadDescriptor
instance in question:
let vimeoUpload = ... // Your instance of VimeoUpload (see above)
let descriptor = ... // The descriptor you'd like to cancel
vimeoUpload.cancelUpload(descriptor: descriptor)
Or by using the identifier
of the OldUploadDescriptor
in question:
let vimeoUpload = ... // Your instance of VimeoUpload (see above)
let identifier = phAsset.localIdentifier
vimeoUpload.cancelUpload(identifier: identifier)
One can access a video's URL by inspecting the link property on the video that's associated with the upload descriptor: descriptor.video.link
.
For example, one could use the results of either the Activation
or Settings
stage to access the video object. See the following method implementation inside of OldUploadDescriptor
:
override public func taskDidFinishDownloading(sessionManager: AFURLSessionManager, task: URLSessionDownloadTask, url: URL) -> URL?
{
...
do
{
switch self.currentRequest
{
case .Create:
...
case .Upload:
...
case .Activate:
// Use the `self.videoURI` to request a video object and inspect its `link`.
self.videoUri = try responseSerializer.process(activateVideoResponse: task.response, url: url, error: error)
case .Settings:
// Or, wait for the Settings step to complete and access `self.video.link`.
self.video = try responseSerializer.process(videoSettingsResponse: task.response, url: url, error: error)
}
}
...
}
Note: ellipses indicate code excluded for brevity.
The following sections provide more insight into the motivation behind VimeoUpload's design, given some identified constraints and goals.
- No Direct Access to PHAsset Source Files
Because of how the Apple APIs are designed, we cannot upload directly from PHAsset source files. So we must export a copy of the asset using an AVAssetExportSession before starting the upload process. Asset export must happen when the app is in the foreground.
- iCloud Photos
If a PHAsset is in iCloud and not resident on device we need to download it to the device before asset export. Download must happen when the app is in the foreground.
- Background Sessions
Because an upload can take a significant amount of time, we must design for the user potentially backgrounding the application at any point in the process. Therefore all requests must be handled by an NSURLSession configured with a background NSURLSessionConfiguration. This means we must rely exclusively on the NSURLSessionDelegate, NSURLSessionTaskDelegate, and NSURLSessionDownloadDelegate protocols. We cannot rely on an NSURLSessionTask subclasses' completion blocks.
- Resumable Uploads
The NSURLSession API does not support resuming an interrupted background upload from an offset. The initial release of this library will use these APIs exclusively and therefore will also not support resuming an upload from an offset.
- Fault Tolerance
The app process could be backgrounded, terminated, or crash at any time. This means that we need to persist the state of the upload system to disk so that it can be reconstructed as needed. Crashes should not adversely impact uploads or the upload system.
- Concurrent File Uploads
We want to be able to conduct concurrent uploads.
- State Introspection
We want to be able to communicate upload progress and state to the user in a variety of locations. Including communicating state for uploads initiated on other devices and other platforms.
- Reachability Awareness
The app could lose connectivity at any time. We want to be able to pause and resume the upload system when we lose connectivity. And we want to be able to allow the user to restrict uploads to wifi.
- Upload Quotas
We need to be able to communicate information to users about their upload quota.
- iOS SDK Quirks
NSURLSessionTask's suspend
method doesn't quite do what we want it to TODO: Provide details
NSURLRequest's and NSURLSessionConfiguration's allowsCellularAccess
properties don't quite do what we want them to TODO: provide details
AFURLSessionManager's tasks
property behaves differently on iOS7 vs. iOS8+ TODO: provide details
And more...
-
A simplified server-side upload API
-
An upload system that addresses each constraint listed above
-
Clear and concise upload system initialization, management, and introspection
-
An upload system that accommodates as many UX futures as possible
TODO
TODO
TODO
TODO
If you find any bugs or technical issues with this library, please create an issue and provide relevant code snippets, specific details about the issue, and steps to replicate behavior.
If you have any questions about using this library, please contact us directly, post in the Vimeo API Forum, or check out StackOverflow.
If you'd like to contribute, please follow our guidelines found in CONTRIBUTING.md.
VimeoUpload
is available under the MIT license. See the LICENSE file for more info.
Tweet at us here: @vimeoapi.
Post on Stackoverflow with the tag vimeo-ios
.
Get in touch here.
Interested in working at Vimeo? We're hiring!