44. Build a Mini Netflix with React in 10 Minutes

Tatsuya Asami
17 min readSep 4, 2018

--

【所要時間】

6時間65分

【概要】

Reactを使った動画サイトのチュートリアル

【要約・学んだこと】

MVP Challenge

ユーザーが短時間でショートムービーをアップロードし、twitterで友人に共有できるサービスをつくる。

機能

  • Userはsign up と log inが必要
  • 登録してログインしたユーザーは約20~30秒のショートビデオをアップロードできる
  • 登録していないユーザーはダッシュボードのプラットフォーム上にある全てのビデオを見ることができる
  • ユーザーはどのビデオもtwitterで共有できる。

Solution

1) Flesh Out The App

まずterminalからminiflixという名前のReactアプリを作成。

create-react-app miniflix
cd miniflix

public/index.htmlを開き、bootstrapをpullし、faviconのリンクの直後に追加する。


<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

2. Set up Authentication & Views

  • auth0-js : 認証のため。
  • react-router: アプリ内のroutingのため
  • jwt-decode: アプリのJSON Web Tokenのデコーディングのため
  • axos :networkリクエストをつくるため
// react-router@3.0.0だとうまくいかない。
npm install auth0-js react-router@3.0.5 jwt-decode axios

src ディレクトリにcomponentsとutilsフォルダを作る。utilsフォルダにAuthService.jsファイルを作成し、このコードを追加する。

components フォルダにCallback.js, Display.js, Nav.js and Upload.js を追加。

  • Callback component
    基本的に認証情報を保存し、アプリのアップロードルートにリダイレクトする。
  • Display component
    全てのビデオを見るためのダッシュボードになる
  • Nav component
    アプリの全てのページのナビゲーションをシェアする
  • Upload component
    登録されたユーザーのビデオのアップロードを操作する

Callback.jsに下記のコードを追加する。

import { Component } from 'react';
import { setIdToken, setAccessToken } from '../utils/AuthService';

class Callback extends Component {

componentDidMount() {
setAccessToken();
setIdToken();
window.location.href = "/";
}

render() {
return null;
}
}

export default Callback;

Nav.jsには下記のコード

import React, { Component } from 'react';
import { Link } from 'react-router';
import { login, logout, isLoggedIn } from '../utils/AuthService';
import '../App.css';

class Nav extends Component {

render() {
return (
<nav className="navbar navbar-default">
<div className="navbar-header">
<Link className="navbar-brand" to="/">Miniflix</Link>
</div>
<ul className="nav navbar-nav">
<li>
<Link to="/">All Videos</Link>
</li>
<li>
{
( isLoggedIn() ) ? <Link to="/upload">Upload Videos</Link> : ''
}
</li>
</ul>
<ul className="nav navbar-nav navbar-right">
<li>
{
(isLoggedIn()) ? ( <button className="btn btn-danger log" onClick={() => logout()}>Log out </button> ) : ( <button className="btn btn-info log" onClick={() => login()}>Log In</button> )
}
</li>
</ul>
</nav>
);
}
}
export default Nav;

Nav componentでは、CSSファイルをインポートしたことを観察しなければいけない。App.cssファイルにこのコードを追加

Display.jsには下記のコードを追加。

import React, { Component } from 'react';
import { Link } from 'react-router';
import Nav from './Nav';
import { isLoggedIn } from '../utils/AuthService';
import axios from 'axios';

class Display extends Component {

render() {

return (
<div>
<Nav />
<h3 className="text-center"> Latest Videos on Miniflix </h3>
<hr/>

<div className="col-sm-12">

</div>
</div>
);
}
}

export default Display;

Upload.jsには下記のコードを追加。

import React, { Component } from 'react';
import { Link } from 'react-router';
import Nav from './Nav';

class Upload extends Component {


render() {

return (
<div>
<Nav />
<h3 className="text-center">Upload Your 20-second Video in a Jiffy</h3>
<hr/>

<div className="col-sm-12">
<div className="jumbotron text-center">
<button className="btn btn-lg btn-info"> Upload Video</button>
</div>
</div>
</div>
);
}
}

export default Upload;

index.jsのコードを置き換え、ルートを設定する。

npm startで実行する。が、エラーが発生。原因不明だが、おそらくブラウザのバグと思われた。

3. Upload Videos

ユーザーがビデオをアップロードする場所が必要。
Cloudinaryはアップロード、ストレージ、管理、操作、配信など、end-to-endで画像や動画の管理を提供するクラウドベースサービスだ。

Cloudinaryのアップロードウィジェットを使う。このウィジェットはローカルコンピュータ、facebook,dropbox,Google Photosなどからビデオをアップロードすることができる。

下記のcloudinary widget scriptをindex.htmlのlinkの後に加える。

<script src="//widget.cloudinary.com/global/all.js" type="text/javascript"></script>

続いてUpload.jsを修正する。

import React, { Component } from 'react';
import { Link } from 'react-router';
import Nav from './Nav';

class Upload extends Component {

uploadWidget = () => {
window.cloudinary.openUploadWidget(
{ cloud_name: 'cloud_name', /*ここを自分のcredentialに変更*/
upload_preset: '<unsigned-preset>', /*ここを自分のcredentialに変更*/
tags: ['miniflix'],
sources: ['local', 'url', 'google_photos', 'facebook', 'image_search']
},

function(error, result) {
console.log("This is the result of the last upload", result);
});
}

render() {
return (
<div>
<Nav />
<h3 className="text-center">Upload Your 20-second Video in a Jiffy</h3>
<hr/>

<div className="col-sm-12">
<div className="jumbotron text-center">
<button onClick={this.uploadWidget} className="btn btn-lg btn-info"> Upload Video</button>
</div>
</div>
</div>
);
}
}

export default Upload;

Cloudinaryは自動的にvideoのtagを提供する。アップロードされた全てのビデオは”miniflix”のtagがつけられる。タグは好きなだけつけることができる。この機能はビデオを探したいときにとても役に立つ

uploadWidget functionでは、cloudinary.openUploadWidget functionをコールし、”Upload Video” buttonを取り付けた。ボタンをクリックするとウィジェットがオープンする。

videoをCloudinaryに直接アップロードし、最近アップロードされたビデオについてのobjectをreturnする。これは公開情報、セキュア、URL、オリジナルファイル名、サムネイルなどを含む。

4. Display Videos

ユーザーが一目でわかるようにアップロードされたビデオを表示する必要がある。 Cloudinary’s react component を使う。

npm install cloudinary-react

Display.jsを下記のコードに修正する。

import React, { Component } from 'react';
import { Link } from 'react-router';
import Nav from './Nav';
import { isLoggedIn } from '../utils/AuthService';
import { CloudinaryContext, Transformation, Video } from 'cloudinary-react';
import axios from 'axios';

class Display extends Component {

state = { videos: [] };

getVideos() {
axios.get('http://res.cloudinary.com/unicodeveloper/video/list/miniflix.json')
.then(res => {
console.log(res.data.resources);
this.setState({ videos: res.data.resources});
});
}
// axious.getでGET通信を行う componentDidMount() {
this.getVideos();
}

render() {

const { videos } = this.state;

return (
<div>
<Nav />
<h3 className="text-center"> Latest Videos on Miniflix </h3>
<hr/>

<div className="col-sm-12">
<CloudinaryContext cloudName="unicodeveloper">
{ videos.map((data, index) => (
<div className="col-sm-4" key={index}>
<div className="embed-responsive embed-responsive-4by3">
<Video publicId={data.public_id} width="300" height="300" controls></Video>
</div>
<div> Created at {data.created_at} </div>

</div>
))
}
</CloudinaryContext>
</div>
</div>
);
}
}

export default Display;

getVideosコードで、1つだけのtagを使うと、特定のtagをもつ全てのビデオを取得するのに役立つCloudinaryのトリックを使う。

http://res.cloudinary.com/unicodeveloper/video/list/miniflix.json

もしvimeo といったタグがあれば、URLは /vimeo.jsonで終わる。
次のコードでは全てのビデオを取得し、videos stateに保存した。

axios.get('http://res.cloudinary.com/unicodeveloper/video/list/miniflix.json')
.then(res => {
console.log(res.data.resources);
this.setState({ videos: res.data.resources});
});

Cloundinary React SDKは4つのメジャーcomopnetを持つ。 Image, Video, Transformation, CloudinaryContextだ。
ここではCloudinaryContextを使う。

renderメソッドで、単にvideos stateをループし、それぞれのvideoをCloudinary Video componentに渡した。
Video componentはCloudinaryからthe public_idを解決し、video urlを取得し、WebページにHTML5 videoを使って表示する。
さらなる利点は、Cloudinaryは自動でブラウザにとってベストなvideo typeを決める。さらに、利用可能なvideo typeと解像度の範囲からベストな選択をすることで、ユーザーはベストな体験ができる。

Transformation component経由のCloudinaryの助けによって、on the flyでvideoを操作できる。

5. Share on Twitter

react twitter widget componentを取得する。

npm install react-twitter-widgets

Display.jsに、componentをimportする。

import { Share } from 'react-twitter-widgets'

ビデオが作られた時間を示すdivの直後に、コードを追加する。



<Share url={`http://res.cloudinary.com/unicodeveloper/video/upload/${data.public_id}.mp4`} />

追加できた。

Conclusion

Cloudinaryは他にも下記の特徴がある。

  • 自動字幕と翻訳
  • ビデオ概要 — アップロードされたビデオから少しの画像を抽出し、ショートビデオ。
  • 自動または手動のビデオマーカー — ビデオの特定の場所をマークし、ユーザーが直接そのポイントにジャンプする。
  • オートビデオタグによる似たvideoを探す。

詳しくはdive in and explore them.

【わからなかったこと】

nodeが上手く機能しなかった。原因は不明だが、おそらくブラウザのバグかライブラリのバグと思われる。現在正常に動作。

実行した解決方法:

  1. 原因があると思われたreact-routerのバージョンを3.0.0から3.0.5に変更。解決せず。
  2. node、react、ソースコード等諸々同じ環境で実行してもらった。そちらでは実行できた。こっちではできない。
  3. エラーは下記の通り。
InvalidTokenError: Invalid token specified: Cannot read property ‘replace’ of undefined
:再生ボタン: 3 stack frames were collapsed.
./src/utils/AuthService.js
src/utils/AuthService.js:1
> 1 | import decode from ‘jwt-decode’;
2 | import { browserHistory } from ‘react-router’;
3 | import auth0 from ‘auth0-js’;
4 | const ID_TOKEN_KEY = ‘id_token’;

3. Invalid token specified: Cannot read property ‘replace’ of undefined で検索するもズバリの解答は発見できず、https://github.com/auth0/jwt-decode/issues/65 の ’InvalidTokenError: Invalid token specified
when logout i am clearing the local storage.’ このコメントを頼りに

4. developerツールからApplication→local storage→clear all

5.正常動作。

【感想】

Reactに既存のサービスを組み合わせると色々なものができるそう。JSの理解、記憶が不十分だと、Reactで分からないのか迷う場面もあるので覚えて行きたい。

--

--

Tatsuya Asami
Tatsuya Asami

Written by Tatsuya Asami

Front end engineer. React, TypeScript, Three.js

No responses yet