Search code examples
javascriptangularvimeovimeo-api

Hot to use Vimeo API to upload a video using Angular?


I have to make an aplication to upload videos to Vimeo. I got some examples from buddies right here on Stack, but could not make it work. Here's the code I assempled from another dev:

vimeo.component.html

<form [formGroup]="vimeoUploadForm">
            <div class="video-inputs">
              <input type="text" placeholder="Vimeo TOKEN key" formControlName="vimeoAPI">
              <input type="text" placeholder="Vimeo Video Name" formControlName="vimeoVideoName">
              <input type="text" placeholder="Vimeo Video Description" formControlName="vimeoVideoDescription">
              <input type="file" (change)="selectFile($event)" multiple #vimeoUpload style="display: none;">
            </div>
          </form>

vimeo.component.ts

import { Component, OnInit } from '@angular/core';

import { FormControl, FormGroup, Validators } from '@angular/forms';
import { VimeoUploadService } from './services/vimeo-upload.service';
import { HttpEventType, HttpResponse } from '@angular/common/http';
import { map, switchMap } from 'rxjs/operators';

@Component({
  selector: 'app-vimeo',
  templateUrl: './vimeo.component.html',
  styleUrls: ['./vimeo.component.scss']
})
export class VimeoComponent implements OnInit {

  public vimeoUploadForm: FormGroup;

  private data: any;
  public uploadPercent;
  // Track upload status by tracking code
  // 0 - Not started
  // 1 - File chosen
  // 2 - Wrong file type
  // 3 - Uploading
  // 4 - Upload error
  // 5 - Upload complete
  public uploadStatus: Number = 0;

  constructor(
    private uploadControl: VimeoUploadService
  ) { }

  selectFile(event): void {
    this.uploadVimeoVideo(event.target.files);
  }

  uploadVimeoVideo(files: FileList): void {
    this.uploadStatus = 1;
    if (files.length === 0) {
      console.log('No file selected!');
      return;
    }
    const file: File = files[0];
    const isAccepted = this.checkAllowedType(file.type);
    if (isAccepted) {
      this.uploadStatus = 1;
      const options = {
        token: this.getFormValue('vimeoAPI'),
        url: 'https://api.vimeo.com/me/videos',
        videoName: this.getFormValue('vimeoVideoName'),
        videoDescription: this.getFormValue('vimeoVideoDescription')
      };
      this.uploadControl.createVimeo(options, file.size)
        .pipe(
          map(data => this.data = data),
          switchMap(
            () => {
              this.uploadControl.updateVimeoLink(this.data.link);
              if (this.data.upload.size === file.size) {
                return this.uploadControl.vimeoUpload(this.data.upload.upload_link, file);
              } else {
                this.uploadStatus = 4;
              }
            }
          )
        ).subscribe(
          event => {
            if (event.type === HttpEventType.UploadProgress) {
              this.uploadPercent = Math.round(100 * event.loaded / event.total);
              this.uploadStatus = 3;
            } else if (event instanceof HttpResponse) {
              this.uploadStatus = 5;
              setTimeout(() => {
                this.uploadStatus = 0;
              }, 5000);
            }
          },
          (error) => {
            console.log('Upload Error:', error);
            this.uploadStatus = 4;
          }, () => {
            console.log('Upload done');
          }
        );
    } else {
      this.uploadStatus = 2;
    }
  }

  initVimeoForm() {
    this.vimeoUploadForm = new FormGroup(
      {
        vimeoAPI: new FormControl('', [Validators.required]),
        vimeoVideoName: new FormControl('', [Validators.required]),
        vimeoVideoDescription: new FormControl('', [Validators.required])
      }
    );
  }

  // HELPERS
  allowUpload(): void {
    this.uploadStatus = 0;
  }

  checkAllowedType(filetype: string): boolean {
    const allowed = ['mov', 'wmv', 'avi', 'flv', 'mp4'];
    const videoType = filetype.split('/').pop();
    return allowed.includes(videoType);
  }

  getFormValue(selector: string) {
    return this.vimeoUploadForm.get(selector).value;
  }

  ngOnInit() {
    // Init Vimeo Data Form
    this.initVimeoForm();
    // Return Vimeo Link from API response
    this.uploadControl.vimeoLinkObs.subscribe(
      data => {
        console.log(data);
      }, error => {
        throw new Error(error);
      }
    );
  }
}

vimeo-upload.service.ts

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

@Injectable()
export class VimeoUploadService {

  vimeoObsShare: Observable<string>;
  vimeoResult: string;

  private vimeoLink = new BehaviorSubject('');
  vimeoLinkObs = this.vimeoLink.asObservable();

  constructor(private http: HttpClient) { }

  updateVimeoLink(val) {
    this.vimeoLink.next(val);
  }

  createVimeo(options, fileSize): Observable<any> {
    // CUSTOM HEADERS FOR A FIRST INIT CALL
    const initHeaders = new HttpHeaders(
      {
        'Authorization': 'Bearer ' + options.token,
        'Content-Type': 'application/json',
        'Accept': 'application/vnd.vimeo.*+json;version=3.4'
      }
    );
    // initHeaders.append('Content-Type', 'application/json');
    // initHeaders.append('Accept', 'application/vnd.vimeo.*+json;version=3.4');
    // CUSTOM INIT BODY
    const initBody = {
      'upload': {
        'approach': 'post',
        'size': fileSize
      },
      "privacy": {
        "embed": "private"       // public for public video
      },
      'name': options.videoName,
      'description': options.videoDescription
    };
    if (this.vimeoResult) {
      return new Observable<any>(observer => {
        observer.next(this.vimeoResult);
        observer.complete();
      });
    } else if (this.vimeoObsShare) {
      return this.vimeoObsShare;
    } else {
      return this.http.post(options.url, initBody, { headers: initHeaders });
    }
  }

  vimeoUpload(url, file: File): Observable<HttpEvent<any>> {
    const headers = new HttpHeaders({
      'Tus-Resumable': '1.0.0',
      'Upload-Offset': '0',
      'Content-Type': 'application/offset+octet-stream'
    });
    const params = new HttpParams();
    const options = {
      params: params,
      reportProgress: true,
      headers: headers
    };
    const req = new HttpRequest('PATCH', url, file, options);
    return this.http.request(req);
  }

}

Error that I get

headers: HttpHeaders {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ}
status: 401
statusText: "Authorization Required"
url: "https://api.vimeo.com/me/videos"
ok: false
name: "HttpErrorResponse"
message: "Http failure response for https://api.vimeo.com/me/videos: 401 Authorization Required"
error:
error: "Unable to upload video. Please get in touch with the app's creator."
link: null
developer_message: "The authentication token is missing a user ID and must be provided when uploading a video."
error_code: 8002

But I'm using this token: b43db728079bbdf962e84fd41b3b37b2 That I got on: https://developer.vimeo.com/apps/167964#personal_access_tokens


Solution

  • First off: never disclose your authentication token in public. Depending on the token's scope, someone could use it to access and modify your account, upload videos to your account, and more.

    Luckily, it's not a problem with the token in your query -- it only has "public" scope and cannot be used for uploading to Vimeo or to access any private metadata. You can verify a token's scope by making a request to https://api.vimeo.com/oauth/verify with that token (docs).

    To upload, you'll need to create a new Authenticated personal access token with the scopes public private create edit upload. From there, use that token to authenticate your POST /me/videos request (docs).

    I hope this information helps!