44. Build a Mini Netflix with React in 10 Minutes

Tatsuya Asami
Sep 4, 2018 · 17 min read

【所要時間】

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で分からないのか迷う場面もあるので覚えて行きたい。

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade