44. Build a Mini Netflix with React in 10 Minutes
【所要時間】
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で実行する。が、エラーが発生。原因不明だが、おそらくブラウザのバグと思われた。
ユーザーがビデオをアップロードする場所が必要。
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を探す。
【わからなかったこと】
nodeが上手く機能しなかった。原因は不明だが、おそらくブラウザのバグかライブラリのバグと思われる。現在正常に動作。
実行した解決方法:
- 原因があると思われたreact-routerのバージョンを3.0.0から3.0.5に変更。解決せず。
- node、react、ソースコード等諸々同じ環境で実行してもらった。そちらでは実行できた。こっちではできない。
- エラーは下記の通り。
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で分からないのか迷う場面もあるので覚えて行きたい。