Uploading videos to Vimeo using tus js client in Angular

Chaman
7 min readJan 6, 2019

--

Vimeo is a popular video sharing website in which users can upload, share , view and stream videos. If you are planning a project which contains sharing and streaming videos then Vimeo can take care of that for you. Vimeo provides API endpoints for all the tasks so that you can implement it in any platform using any language. We’ll be implementing video upload feature in an Angular application with tus js client.

Step 1: Setting up Vimeo account for upload access

Create a Vimeo account and verify your email address, go to https://developer.vimeo.com/apps to register your application by providing necessary details. Now you need to request for video upload access in your app settings > Permissions and wait for approval. After you get permission for upload you can generate your access token in Authentication > Generate Access Token. Give "Edit" and "Upload" scopes to your token and click "Generate".

token scopes
ACCESS TOKEN

Copy or save your access token. You will be using this access token for authenticating while making API calls. Test your access token by making a HTTP GET request to "https://api.vimeo.com/me" with Authorization>Bearer Token and paste your access token in postman.

API REQUEST

You should get a JSON response with user details for a successful API call. The API docs is available here Vimeo API playground.

Step 2: Creating the uploader

There are 3 steps to upload your video successfully to Vimeo.

  1. Create the video
  2. Upload the video file
  3. Verify the upload

Create the video

Prerequisites: Knowledge about RxJS concepts like observables, map, pipe, expand, EMPTY.

In this step you are required to make an authenticated HTTP POST request to https://api.vimeo.com/me/videos with the video details like file size and upload approach in the request body so that a video will be created in Vimeo with the given details. Video name and other attributes can also be sent in this request but the above 2 attributes are mandatory.

{
"name": "{filename}",
"upload": {
"approach": "tus",
"size": "{size}"
}
}

The POST request should contain the following headers.

Authorization bearer {access_token} Content-Type application/json Accept application/vnd.vimeo.*+json;version=3.4

I’ll be creating a service “upload” for this and inject this service in the required component. ng g service upload

Now in your upload service import the following

//Used for asynchronous http request handling
import { Observable } from 'rxjs';

//Required to set custom headers and make http requests
import { HttpHeaders, HttpClient } from '@angular/common/http';

Pass HttpClient as constructor parameter.

constructor(private http: HttpClient) { }

Set your access token and Vimeo API endpoint.

private api = 'https://api.vimeo.com/me/videos';

//It's advised not to hardcode the API token, instead fetch it from the server after authentication.
private accessToken = 'youraccesstoken';

Now we’ll write the createVideo function which is responsible for creating a video in your Vimeo account. The function takes a single video file of type File as a parameter and returns an observable i.e. the HPPT POST request.

Current progress:

//Filename: upload.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpHeaders, HttpClient } from '@angular/common/http';

@Injectable({
providedIn: 'root'
})
export class UploadService {
constructor(private http: HttpClient) { }
private api = 'https://api.vimeo.com/me/videos';
private accessToken = 'youraccesstoken';

createVideo(file: File): Observable<any> {
const body = {
name: file.name,
upload: {
approach: 'tus',
size: file.size
}
};
console.log(file);
const header: HttpHeaders = new HttpHeaders()
.set('Authorization', 'bearer ' + this.accessToken)
.set('Content-Type', 'application/json')
.set('Accept', 'application/vnd.vimeo.*+json;version=3.4');
return this.http.post(this.api, body, {
headers: header,
observe: 'response'
});
}
}

Now go to the component where you are implementing the upload feature. I’ll be implementing it in upload.component.ts.

  • Create a file input that accepts videos only.
  • Add an upload button to start the process.
  • Inject your upload service into the component.

I’ll be creating a class uploadFiles for storing the video name, web address for uploading the video & URI of the uploaded video on Vimeo.

// Filename: upload.compopnent.ts
export class uploadFiles {
constructor(public video: File, public path: string, public uploadURI: string) {
this.video = video;
this.path = path;
this.uploadURI = uploadURI;
}
}

Create a function getLink which takes video file, current video index, video array as parameters. This function will pass the video to the createVideo function in the upload service. Here you can see that the index is incremented so that the process can be repeated on the entire array.

For every response received, you need to create a new uploafFiles object and save the upload link and video link from the successful response body. I've created a pendingFiles array to store all the video's response details for uploading later.

// Filename: upload.component.ts
getLink = (video: File, index, arr) => {
console.log('index: ' + index);
return this.upload.createVideo(video).pipe(
map(response => {
const videoFile = new uploadFiles(video, response.body.link, response.body.upload.upload_link);
this.pendingFiles.push(videoFile);
console.log('response: ' + response);
return {
data: response,
index: index + 1,
arr: arr
};
})
);
}

Create a start function to run when "Upload" button is clicked. The above getLink function has to be called for all the videos in the array, so we use the rxjs expand operator to return the next video for upload until the last video in the array.

// Filename: upload.component.ts
public start(files: FileList) {
this.videoList = files;
console.log(this.videoList);

const recursion = this.getLink(this.videoList[0], 0, this.videoList).pipe(expand(res => {
return res.index > res.arr.length - 1 ?
EMPTY : this.getLink(this.videoList[res.index], res.index, this.videoList);
}));

recursion.subscribe(x => {
if (x.index > x.arr.length - 1) {
console.log('Links generated, Starting upload');
// All links have been generated now you can start the upload process here
}
});
}

Now you can test the upload link generation feature. Once the video is created and the link is generated, you can go to your Vimeo account and see the new videos created. The response will contain upload.upload_link which is the URL for uploading that video.

Current progress:

upload.component.ts

import { Component, OnInit } from '@angular/core';
import { UploadService } from '../upload.service';
import { map, expand } from 'rxjs/operators';
import { EMPTY } from 'rxjs';

export class uploadFiles {
constructor(public video: File, public path: string, public uploadURI: string) {
this.video = video;
this.path = path;
this.uploadURI = uploadURI;
}
}

@Component({
selector: 'app-upload',
template: `<div style="text-align:center">
<input type="file" #file name="video" id="video" accept="video/*" multiple>
<input type="button" value="Upload" (click)="start(file.files);"> </div>`,
styleUrls: ['./upload.component.css']
})

export class UploadComponent implements OnInit {
title = 'vimeo-uploader';
videoList: FileList;
videoLinks = [];
pendingFiles: uploadFiles[] = [];
constructor(private upload: UploadService) { }

ngOnInit() {}

public start(files: FileList) {
this.videoList = files;
console.log(this.videoList);
const recursion = this.getLink(this.videoList[0], 0, this.videoList).pipe(expand(res => {
return res.index > res.arr.length - 1 ?
EMPTY : this.getLink(this.videoList[res.index], res.index, this.videoList);
}));

recursion.subscribe(x => {
if (x.index > x.arr.length - 1) {
console.log('Link generated, Starting upload');
// All links have been generated now you can start the upload
}
});
}

getLink = (video: File, index, arr) => {
console.log('index: ' + index);
return this.upload.createVideo(video).pipe(
map(response => {
const videoFile = new uploadFiles(video, response.body.link, response.body.upload.upload_link);
this.pendingFiles.push(videoFile);
console.log('response: ' + response);
return {
data: response,
index: index + 1,
arr: arr
};
})
);
}
}

upload.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpHeaders, HttpClient } from '@angular/common/http';

@Injectable({
providedIn: 'root'
})
export class UploadService {
constructor(private http: HttpClient) { }
private api = 'https://api.vimeo.com/me/videos';
private accessToken = 'yourapitoken';

createVideo(file: File): Observable<any> {
const body = {
name: file.name,
upload: {
approach: 'tus',
size: file.size
}
};
console.log(file);
const header: HttpHeaders = new HttpHeaders()
.set('Authorization', 'bearer ' + this.accessToken)
.set('Content-Type', 'application/json')
.set('Accept', 'application/vnd.vimeo.*+json;version=3.4');
return this.http.post(this.api, body, {
headers: header,
observe: 'response'
});
}
}

Upload the video

We’ll be using tus-js-client library for uploading the videos. Install the library & its type definitions and import the library.

npm install --save tus-js-client
npm install --save-dev @types/tus-js-client

import * as tus from 'tus-js-client';

Since we are using tus-js-client it will handle the process of uploading, we just have to specify the URL for uploading that we get in the previous successful response and set the retry delays according to your requirements and thats it.

Create a new tus.Upload object with necessary parameters inside a function in upload service so that it can be called from component.

// Filename: upload.service.ts
public tusUpload(
file: uploadFiles,
i: number,
videoArray: uploadFiles[],
uploadArray: tus.Upload[],
success: any,
): tus.Upload {
const upload = new tus.Upload(file.video, {
uploadUrl: file.uploadURI,
endpoint: file.uploadURI,
retryDelays: [0, 1000, 3000, 5000],
onError: error => {
console.log('Failed: ' + file.video.name + error);
},
onProgress: (bytesUploaded, bytesTotal) => {
const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2);
console.log(
'file: ' + i + ' of ' + (videoArray.length - 1) + ':',
bytesUploaded,
bytesTotal,
percentage + '%'
);
},
onSuccess: () => {
console.log('Download' + file.video.name + 'from' + upload.url);
if (i < videoArray.length - 1) {
uploadArray[i + 1].start();
} else {
success();
console.log('Videos uploaded successfully');
}
}
});
return upload;
}

Create one more function in the upload component or you can directly call the tusUpload function after successfully creating the links.

// Filename: upload.component.ts
videoUpload() {
const success = () => {
console.log('after video upload section');
};
const upload: Array<any> = [];
for (let i = 0; i < this.pendingFiles.length; i++) {
upload.push(
this.upload.tusUpload(
this.pendingFiles[i],
i,
this.pendingFiles,
upload,
success
)
);
}
console.log('start video upload sequentially');
upload[0].start();
}

Note: Don’t forget to call videoUpload function after generating links, otherwise the video wont start uploading.

Current progress:

Note: This is one way to implement, if you want less memory usage then you can skip the unnecessary array implementations.

If one video fails then next video can start to upload by using similar implementation of onSuccess method of tus.Upload in onError method. DevTools console and network tab will come handy during debugging.😉

Originally published at zocada.

--

--