質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

1回答

1859閲覧

Node.jsのStreamをつかって復数の動画から1つの動画を選択して再生する仕組み

TMoon

総合スコア1

HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

1クリップ

投稿2022/05/25 02:08

編集2022/05/25 06:15

実現したいこと

ローカルフォルダに保存されている復数の動画ファイルから1つを選択し、フロントエンドで動画を再生したいと考えています。

作成したコード

下記のようなコードを作成しました。
実際にNode.js、EJSを使用して、動画のパス部分を可変にして、フロントエンドから選択できるようにコード作成しました。

javascript

1var fs = require('fs'); 2var glob = require("glob") 3var express = require("express"); 4 5const port = 3000; 6var app = express(); 7var videoPath = ""; 8 9app.set("view engine", "ejs"); 10app.get('/', function (req, res) { 11 res.render('./sample.ejs', { 12 //ファイルのリストを渡す 13 files: mp4files = glob.sync("video/*.mp4"), 14 path: videoPath, 15 }); 16}); 17 18app.get('/renderVideo', function (req, res) { 19 const path = videoPath 20 const stat = fs.statSync(path) 21 const fileSize = stat.size 22 const range = req.headers.range 23 if (range) { 24 const parts = range.replace(/bytes=/, "").split("-") 25 const start = parseInt(parts[0], 10) 26 const end = parts[1] 27 ? parseInt(parts[1], 10) 28 : fileSize - 1 29 const chunksize = (end - start) + 1 30 const file = fs.createReadStream(path, { start, end }) 31 const head = { 32 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 33 'Accept-Ranges': 'bytes', 34 'Content-Length': chunksize, 35 'Content-Type': 'video/mp4', 36 } 37 res.writeHead(206, head); 38 file.pipe(res); 39 40 } else { 41 const head = { 42 'Content-Length': fileSize, 43 'Content-Type': 'video/mp4', 44 } 45 res.writeHead(200, head) 46 fs.createReadStream(path).pipe(res) 47 } 48}); 49 50var bodyParser = require('body-parser'); 51app.use(bodyParser.urlencoded({ extended: false })); 52app.post('/getMp4Name', function (req, res) { 53 videoPath = req.body.mp4 54 res.redirect("/") 55 console.log("videoPath : ", videoPath) 56}); 57 58app.listen(port, () => console.log('app listening on port 3000!'))

HTML

1<!DOCTYPE html> 2<html lang="ja"> 3 4<head> 5 <meta charset="utf-8" /> 6 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> 7 <title>Test Application</title> 8</head> 9 10<body> 11 <!-- 保存されている動画のファイル名を取得 --> 12 <div> 13 <form method="POST" id="tableForm" action="/getMp4Name"> 14 <select class="mp4" name="mp4"> 15 <% for (var i=0; i<files.length; i++){ %> 16 <option value=<%=files[i] %> ><%=files[i] %> 17 </option> 18 <%} %> 19 </select> 20 <input type="submit"> 21 </form> 22 </div> 23 <!-- 動画表示 --> 24 <figure> 25 <video id="displayVideo" controls style="max-width: 500px;"> 26 <source src="http://localhost:3000/renderVideo/" type="video/mp4" /> 27 </video> 28 </figure> 29</body> 30 31</html>

参考にしたこと

上記のコードは下記のリンクにある説明を参考にしました。
Video Stream With Node.js and HTML5
こちらではNode.jsのStreamをつかい、パスは決め打ちで単体の動画を再生する方法について説明されています。

/videoでGETされたときに下記が呼び出され、HTMLで表示されます。

javascript

1//Node.js 2app.get('/video', function(req, res) { 3 //ここのパスを可変にした。 4 const path = 'assets/sample.mp4' 5 const stat = fs.statSync(path) 6 const fileSize = stat.size 7 const range = req.headers.range 8 if (range) { 9 const parts = range.replace(/bytes=/, "").split("-") 10 const start = parseInt(parts[0], 10) 11 const end = parts[1] 12 ? parseInt(parts[1], 10) 13 : fileSize-1 14 const chunksize = (end-start)+1 15 const file = fs.createReadStream(path, {start, end}) 16 const head = { 17 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 18 'Accept-Ranges': 'bytes', 19 'Content-Length': chunksize, 20 'Content-Type': 'video/mp4', 21 } 22 res.writeHead(206, head); 23 file.pipe(res); 24 } else { 25 const head = { 26 'Content-Length': fileSize, 27 'Content-Type': 'video/mp4', 28 } 29 res.writeHead(200, head) 30 fs.createReadStream(path).pipe(res) 31 } 32});

HTML

1<video id="videoPlayer" controls> 2 <source src="http://localhost:3000/video" type="video/mp4"> 3</video>

試したこと、考えていること

先程のリンクを参考に、path部分を可変にして(フロントエンドから選択できるようにして)改良してみました。しかし動画を切り替えることができませんでした。
パスが変更されたあと、下記のようなエラーコードがでました。

RangeError [ERR_OUT_OF_RANGE]: The value of "start" is out of range. It must be <= "end"

推測ですが先ほど紹介したコードで、パスを変更する前にapp.get内でpipe()してレスポンスを返している部分をunpipe()する必要があると考えました。

javascript

1file.pipe(res);

質問したいこと

上記のやり方で、動画のパスを切り替えてStreamするためにはどのような処理が必要でしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miyabi-sun

2022/05/25 05:29 編集

問題というのは「理想」と「現実」のギャップを指します。 理想は「path部分を可変にして(フロントエンドから選択できるようにして)改良してみました。」で 現実は「しかし動画を切り替えることができませんでした。」ですね。 コードは書いたようにしか動いてくれません。 恐らく「技術力不足等の要因で、言ってることとやってる事が違う」状態になってるのが問題の根本と推測されます。 しかし今回の質問文のように過去の動く時点のコードを見せられても、 これじゃ「理想と過去」だよって話になってしまうんですよね。 問題は「理想と現実」のギャップなので、これは問題とは呼べないし、回答もアドバイスもし辛いです。 というわけで「駄目になった時点のコード」を載せましょう。 これで「理想と現実」を比較出来るようになるので回答者側がぐっと回答しやすくなります。 最初のプロトタイプのコードは、それに対する比較として役に立つので、 そのまま追加するだけで大丈夫です。
TMoon

2022/05/25 05:49

コメントありがとうございます。 > 「技術力不足等の要因で、言ってることとやってる事が違う」 その通りです。 再度、「理想と現実」がはっきりするよう質問を編集させていただきます。
miyabi-sun

2022/05/25 08:03

対応ありがとうございます。 ぐっと良くなりましたね!
TMoon

2022/05/25 08:13

Qiitaの記事(「何故teratailは叩かれるのか」)を読まさせていただきました。普段Qiitaやnoteなどで文章を書くのですが、質問する際の文章は下手くそなのだと認識しました。 今後は注意して質問文をねりたいと思います。 ご指導ありがとうございました!
guest

回答1

0

ベストアンサー

先程のリンクを参考に、path部分を可変にして(フロントエンドから選択できるようにして)改良してみました。しかし動画を切り替えることができませんでした。

コードと合わせて何がやりたいのか理解しました。
実現する為にかなり頑張った形跡が伺えますね。

Webサーバというのは不特定多数のユーザからわーっとHTTPリクエストを受け取るものなので
A→B→Cの順番にリクエストを「するだろう」という前提で作ってはいけません。
そういう事情から、Express.js流儀の解決方法を取る必要があります。

これは複数の思想が絡みあう事になるので、
一つの正解の形をコードで先に提示した方が良いでしょう。

js

1// 変数は全てconstで定義するべき、可変させる一部のみletで宣言する 2const fs = require('fs'); 3// 行末のセミコロンは入れるなら入れる、無いなら全て抜くで統一しよう 4const glob = require("glob"); 5const express = require("express"); 6// Node.jsの推奨コーディングルールとしてrequirieは上に固めろというのがある 7const bodyParser = require('body-parser'); 8 9const port = 3000; 10const app = express(); 11// グローバル変数videoPathを中で書き換えるのは 12// 不特定多数のユーザーが順不同でアクセスするWebサーバとしては扱いづらいので変数ごと削除した 13 14// 字下げはファイルの中で統一しよう、今回は半角2文字分にした 15app.set("view engine", "ejs"); 16// 関数は全てアロー関数でOK 17app.get('/', (req, res) => { 18 // 汎用オブジェクトの中で変数への代入は可読性が死ぬほど落ちるのでやめよう 19 // そもそも変数宣言しなくても実現出来るので取り除いた、videoPathは用途不明なので捨てた 20 res.render('./sample.ejs', { 21 // ファイルパスを指定するときは、起点となる__dirnameを使うと環境差異が出づらくなる 22 files: glob.sync(`${__dirname/}/video/*.mp4`), 23 }); 24}); 25 26// 任意のパスを実現するときは、ルートパラメータ「:paramName」を利用すべし 27app.get('/video/:fileName', (req, res) => { 28 const path = `${__dirname}/video/${req.params.fileName}`; 29 // 可変ファイルパスにするならfs.existsSyncで存在確認をすべき、ついでに早期returnで無ければ逃げるようにしてみた 30 if (!fs.existsSync(path)) { 31 res.status(404).send("そんな動画ファイルないぞ"); 32 return; 33 } 34 35 // ファイルがあることが証明されたので、改めてムービーを生成 36 const stat = fs.statSync(path); 37 const fileSize = stat.size; 38 // ユーザーのリクエストに引っ付いてきたパラメータを信じない方が良いと思う、一応そのまま 39 const range = req.headers.range; 40 if (range) { 41 const parts = range.replace(/bytes=/, "").split("-"); 42 const start = parseInt(parts[0], 10); 43 const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1; 44 const head = { 45 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 46 'Accept-Ranges': 'bytes', 47 // 状態変数が増えると可読性が悪くなるので一度しか使わないものは宣言しない 48 'Content-Length': (end - start) + 1, 49 'Content-Type': 'video/mp4', 50 } 51 res.writeHead(206, head); 52 fs.createReadStream(path, { start, end }).pipe(res); 53 } else { 54 const head = { 55 'Content-Length': fileSize, 56 'Content-Type': 'video/mp4', 57 } 58 res.writeHead(200, head) 59 fs.createReadStream(path).pipe(res) 60 } 61}); 62 63// リクエストに入ってる内容を返すだけって存在価値ある? 64// 多分不要 65app.use(bodyParser.urlencoded({ extended: false })); 66// RESTfulというWebサイトの思想によると、 67// メソッドのGETは受け取る、POSTは追加、PUTは修正、DELETEは削除 68// メソッドPOSTでパスにgetが入ってるのはなんやねんってなるのでgetの文字列削った方が良さそう 69app.post('/getMp4Name', (req, res) => { 70 videoPath = req.body.mp4 71 res.redirect("/") 72 // videoPath変数削ったからエラー出ると思う 73 console.log("videoPath : ", videoPath) 74}); 75 76// 動画の長さ等のデータをチェックする時はこういうエンドポイント用意して確認する方が良いかもね 77app.get("/videoInfo/:fileName", (req, res) => { 78 // /videoと同じファイル存在チェック 79 const path = `${__dirname}/video/${req.params.fileName}`; 80 if (!fs.existsSync(path)) { 81 res.status(404).send("そんな動画ファイルないぞ"); 82 return; 83 } 84 const stat = fs.statSync(path); 85 const fileSize = stat.size; 86 87 // とりまファイルサイズだけ、動画の再生時間とか色々をJSONで返す 88 res.json({fileSize}); 89}); 90 91app.listen(port, () => console.log('app listening on port 3000!'))

一番に見るべきポイントとしては、パスの所で/video/:fileNameなどと指定して
任意のパスで受け取れるようにすることです。
参考記事: ルーティングガイド - Expressドキュメント

teratailの質問ページの「https://teratail.com/questions/qx8ag3sq852k0a」みたいなもんですね。
qx8ag3sq852k0aの部分が質問ID

他、私が仕事でコード書くならこう変更するわという内容をコメント付きで盛り込んでいます。
別にこのままでええわな箇所は覚え無くても良いですが、参考になった箇所は覚えて帰ってくださいね。

投稿2022/05/25 10:19

miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TMoon

2022/05/25 23:37

丁寧なコメント、大変ありがとうございます。 手元の環境で試してみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問