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

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

新規登録して質問してみよう
ただいま回答率
85.35%
Webサーバー

Webサーバーとは、HTTPリクエストに応じて、クライアントに情報を提供するシステムです。

JavaScript

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

Raspberry Pi

Raspberry Piは、ラズベリーパイ財団が開発した、名刺サイズのLinuxコンピュータです。 学校で基本的なコンピュータ科学の教育を促進することを意図しています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

Q&A

解決済

1回答

4058閲覧

Python の FastAPI で出力した MJPG-streamer を video タグに描画したい

harukat

総合スコア1

Webサーバー

Webサーバーとは、HTTPリクエストに応じて、クライアントに情報を提供するシステムです。

JavaScript

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

Raspberry Pi

Raspberry Piは、ラズベリーパイ財団が開発した、名刺サイズのLinuxコンピュータです。 学校で基本的なコンピュータ科学の教育を促進することを意図しています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

0グッド

0クリップ

投稿2021/06/30 13:41

編集2021/07/05 03:06

前提・実現したいこと

Javascript を使って、 MJPG-streamer のストリーミング画像をvideoタグに描画しようとしています。
しかし、 Canvas には画像が出力され、動画のように更新されているものの、 video タグの方では表示されませんでした。
captureStream() を使って video の srcObject とする正しい方法を教えていただけますと幸いです。

以下のリンクを参考に実装しました。
https://zenn.dev/zgw426/articles/ed632c88148183e9b902
https://qiita.com/akmtsyk/items/bb38947f028bf11fe72b

発生している問題・エラーメッセージ

console を確認したところ、エラーは発生していませんでした。
video タグではローディングが続く状態です。

該当のソースコード

##HTML側
以下のコードで MJPEG を video タグに変換して表示させようと試みています。
drawCanvasFromMjpegStrmer()はおそらく問題なく動作しており、 canvas タグは更新されています。

HTML

1 <!-- test.html --> 2 <html> 3 <head> 4 <title>Video Streaming Demonstration</title> 5 </head> 6 <body> 7 <h1>Video Streaming Demonstration</h1> 8 9 <canvas id="canvas", width="1280px", height="720px"></canvas> 10 <video id="player-canvas" controls autoplay loop muted poster="" playsinline width="1280px" height="720px"></video> 11 12 <script type="text/javascript"> 13 let canvasStream = null; 14 15 function drawCanvasFromMjpegStrmer(){ //MJPG-streamer -> CANVAS-TAG 16 const canvas = document.getElementById("canvas"); 17 const ctx = canvas.getContext("2d"); 18 setInterval(() => { 19 if (canvas && ctx){ 20 const chara = new Image(); 21 chara.src = "http://localhost:8000/video_feed"; // MJPG-streamer のURL 22 chara.onload = () => { 23 ctx.drawImage(chara, 0, 0); 24 }; 25 } 26 }, 10000/60); 27 } 28 29 function drawVideoFromCanvas() { // CANVAS-TAG -> VIDEO-TAG 30 const canvas = document.querySelector("canvas"); 31 const video = document.getElementById("player-canvas"); 32 canvasStream = canvas.captureStream(30); 33 video.srcObject = canvasStream; 34 } 35 36 drawCanvasFromMjpegStrmer(); 37 drawVideoFromCanvas(); 38 </script> 39 </body> 40</html>

###サーバー(Python)側
サーバーライブラリには、fastAPI を採用し、実装しています。

Python

1import sys 2import uvicorn 3 4from fastapi import FastAPI, Request 5from fastapi.responses import HTMLResponse, StreamingResponse 6from fastapi.staticfiles import StaticFiles 7from fastapi.templating import Jinja2Templates 8 9import numpy as np 10import cv2 11import time 12 13app = FastAPI() 14 15app.mount("/static", StaticFiles(directory="static"), name="static") 16templates = Jinja2Templates(directory="templates") 17 18 19 20@app.get("/", response_class=HTMLResponse) 21async def index(request: Request): 22 return templates.TemplateResponse('index.html', {"request": request}) 23 24 25def gen(): 26 """Video streaming generator function.""" 27 bw_flag = True 28 # 白黒画像の切り替えを繰り返す 29 while True: 30 if bw_flag: 31 img = np.zeros((1280,720,3), dtype=np.uint8) 32 else: 33 img = np.ones((1280,720,3), dtype=np.uint8) * 255 34 if type(img) == np.ndarray: 35 frame = cv2.imencode('.jpg', img)[1].tobytes() 36 yield (b'--frame\r\n' 37 b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') 38 else: 39 print("img is None.") 40 time.sleep(0.5) 41 bw_flag = not bw_flag # 白黒反転 42 43 44@app.get('/video_feed', response_class=HTMLResponse) 45async def video_feed(): 46 """Video streaming route. Put this in the src attribute of an img tag.""" 47 return StreamingResponse(gen(), 48 media_type='multipart/x-mixed-replace; boundary=frame') 49 50def start_server(): 51 uvicorn.run(app, host="0.0.0.0", port=8000) 52 53if __name__ == "__main__": 54 start_server()

以下のコードはtemplates/index.htmlに配置し、localhost:8000でアクセスしたときに表示される画面です。

HTML

1 <!-- index.html --> 2 <html> 3 <head> 4 <title>Video Streaming Demonstration</title> 5 </head> 6 <body> 7 <h1>Video Streaming Demonstration</h1> 8 <!-- <img src="{{ url_for('video_feed') }}"> --> 9 <canvas id="canvas", width="1280px", height="720px"></canvas> 10 <video id="player-canvas" controls autoplay loop muted poster="" playsinline width="1280px" height="720px"></video> 11 <!-- jinja2のテンプレートの書き方です。/video_feedを呼び出しています。 --> 12 <script type="text/javascript"> 13 function drawCanvasFromMjpeg(){ //MJPG-streamer -> CANVAS-TAG 14 const canvas = document.getElementById("canvas"); 15 const ctx = canvas.getContext("2d"); 16 setInterval(() => { 17 if (canvas && ctx){ 18 const chara = new Image(); 19 chara.src = "{{ url_for('video_feed') }}" // URLをfastapiから取得(localhost:8000/video_feed) 20 chara.onload = () => { 21 ctx.drawImage(chara, 0, 0); 22 }; 23 } 24 }, 10000/60); 25 } 26 27 function drawVideoFromCanvas() { // CANVAS-TAG -> VIDEO-TAG 28 const canvas = document.getElementById("canvas"); 29 const video = document.getElementById("player-canvas"); 30 const ctx = canvas.getContext('2d'); 31 32 // ctx.drawImage(video, 0, 0, canvas.width, canvas.height); 33 canvasStream = canvas.captureStream(30); 34 video.srcObject = canvasStream; 35 } 36 drawCanvasFromMjpeg(); 37 drawVideoFromCanvas(); 38 </script> 39 </body> 40</html>

試したこと

ブラウザの問題かと思い、 Firefox で試しましたが、動作しませんでした。
Fastapi を用いたローカルサーバー上のページ(localhost:8000)を Chrome で開くと、 video タグ側でも表示されました。
test.htmlを直に開くと canvas のみ動画が表示されました。

補足情報(FW/ツールのバージョンなど)

OS: Linux (OpenSUSE Leap 15.3)
Python: 3.8.10(Pyenv)
ブラウザ:

  • Google Chrome Stable: ver. 91.0.4472
  • Firefox: ver. 78.11.0

皆様のご助力に感謝いたします。
よろしくお願いいたします。

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

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

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

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

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

guest

回答1

0

自己解決

自己解決したため、解決策を残します。
#原因
サーバーがクロスオリジン準拠の方法で画像を送信するできておらず、また、crossorigin属性を使用してこの画像をリクエストしていなかった。

オリジン間リソース共有 (CORS)

オリジン間リソース共有Cross-Origin Resource Sharing (CORS) は、追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組みです。ウェブアプリケーションは、自分とは異なるオリジン (ドメイン、プロトコル、ポート番号) にあるリソースをリクエストするとき、オリジン間 HTTP リクエストを実行します。
(MDN web docs オリジン間リソース共有(CORS): https://developer.mozilla.org/ja/docs/Web/HTTP/CORS より引用)

#解決策
サーバーとサイトをCross-Origin Resource Sharing(CORS)に対応させる。
###FastAPIを修正

Python

1from fastapi import FastAPI 2from starlette.middleware.cors import CORSMiddleware # 追加 3 4app = FastAPI() 5 6# CORSを回避 7app.add_middleware( 8 CORSMiddleware, 9 allow_origins=["*"], 10 allow_credentials=True, # 追記により追加 11 allow_methods=["*"], # 追記により追加 12 allow_headers=["*"] # 追記により追加 13) 14 15## 以下省略 ##

HTMLを修正

HTML

1<html> 2 <head> 3 <title>Video Streaming Demonstration</title> 4 </head> 5 <body> 6 <h1>Video Streaming Demonstration</h1> 7 <!-- <img src="{{ url_for('video_feed') }}"> --> 8 <canvas id="canvas", width="1280px", height="720px" hidden></canvas> 9 <video id="player-canvas" controls autoplay loop muted poster="" playsinline width="1280px" height="720px"></video> 10 <!-- jinja2のテンプレートの書き方です。/video_feedを呼び出しています。 --> 11 <script type="text/javascript"> 12 function drawCanvasFromMjpegStremer(){ //MJPG-streamer -> CANVAS-TAG 13 const canvas = document.getElementById("canvas"); 14 const ctx = canvas.getContext("2d"); 15 setInterval(() => { 16 if (canvas && ctx){ 17 const chara = new Image(); 18 chara.crossOrigin = 'anonymous'; // 追記! 19 chara.src = "http://localhost:8000/video_feed"; 20 chara.onload = () => { 21 ctx.drawImage(chara, 0, 0); 22 }; 23 } 24 }, 10000/60); 25 } 26 27 function drawVideoFromCanvas() { // CANVAS-TAG -> VIDEO-TAG 28 const canvas = document.getElementById("canvas"); 29 const video = document.getElementById("player-canvas"); 30 const ctx = canvas.getContext('2d'); 31 32 // ctx.drawImage(video, 0, 0, canvas.width, canvas.height); 33 canvasStream = canvas.captureStream(30); 34 video.srcObject = canvasStream; 35 } 36 drawCanvasFromMjpegStremer(); 37 drawVideoFromCanvas(); 38 </script> 39 </body> 40</html>

ローカルサーバーへのアクセスは制限があることを学びました。

投稿2021/07/06 06:19

harukat

総合スコア1

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問