UV4Lを使う(6)

前回UV4Lを使った自前のHPを作りました。今回はRaspberry PIにPythonのWebserverを立ち上げてUV4Lと互いに通信出来るようにしたいと思います。

先ずは、現状の再確認

現在の状況をまとめると以下の様になっています。

  • NoteBook側
    • ホームディレクトリに、”intercom.html”(実際にはその他のファイルもあるが)を保存
    • そのファイルを、ブラウザ(FireFox)で実行。
  • Raspberry PI側
    • 起動時と共に、”UV4L”が起動。UV4L内には
      • Web_Server(http://raspberrypi.local:8090でアクセス出来るサーバー)
      • Socket Server(P2P通信を行う前に互いの情報を交換するサーバー)
      • その他、Stream Server, WebRTC Server等が有る
  • 互いの通信
    • 互いの通信は、P2Pなので間にサーバーを通していない。
    • HPを通して、Raspberry からの動画と音声、NoteBookからの音声をStreaming出来る。
    • NoteBook上でRaspberryからの動画と音声の録音が可能。

操作はWebブラウザで行い録画等も可能ですが、例えば

  • 暗くなったのでライトを付けたい
  • 留守中の訪問者の録画を行いたい

と思ってもこの状態では出来ません。そこで、”Mjpg-Stremer & FFmpegを使う”の時と同じ様に、UV4LをMjpg-Streamerに見立てて、構成を以下の様に変更してみました。

  • Raspberry PI側
    • PythonのWebserverを立ち上げる:ポートを8010とする。
    • WebServerのHPとして、”intercom.html”を使用する。
    • PythonのWebserverとUV4L間で通信する。
    • ライト点灯や留守録は、PythonのWebserverにコードを追加して行う。
  • NoteBook側
    • HPをRaspberry側に移動したので、PythonのWebserver(ポート8010)にアクセスしHPを表示させる。

この構成、実際にコーディングしてみたら思わぬ自体になりました。

navigator.mediaDevices.getUserMedia()には使用制限が有った

コード自体は簡単で作成出来、Raspberry PIからの映像と音声のStramingは出来たのですが、NoteBook側からRaspberry PIに音声を送ろうとした時にエラーが出ました。エラーの内容は、このブラウザでは、navigator.mediaDevices.getUserMedia()が使えないというものでした。navigator.mediaDevices.getUserMedia()には使用制限が有り、プロトコルが以下の時に使用できる様です

  • HPPTS:/
  • file:///
  • または、localhostからLoadされたもの

今回はPython WebServerのプロトコルがHTTPでHPPTSでは無いのが原因の様です。これでは、”Mjpg-Stremer & FFmpegを使う”と同様NoteBook側から音声が送れないのでHPのコード ”intercom.html” をNoteBook側に戻す事にしました。

今度はPython WebServerからのデータが受けれない

今回の構成は、”intercom.html”をNoteBook側に戻し、Python WebServerは残しています。

この状態下FirFoxで、”intercom.html”を実行。Raspberry PIからの音声と動画、NoteBookからの音声のStreamingは問題なく出来るようになりました。またHPからPython Web Serverに命令を送ってLED等を点灯が出来る様になったのでが、Python Web Serverからのデータを受ける事が出来ません。表示されるエラーメッセージは、’Access-Control-Allow-Origin’ missingです。オリジンが違うと言う事みたいですが、オリジンって何?

オリジンとは

オリジンとは、プロトコル、ドメイン、ポートをまとめて呼ぶようです。今回のケースですが、NoteBookで”intercom.html”を実行していますのでオリジンは、file:///home//intercom/intercom.html。このコード以下を実行しています。この場合のオリジンは、”http://raspberrypi.local:8010″。確かに互いのオリジンは全く違います。

    	        var xhr = new XMLHttpRequest();	
        	    xhr.open('GET', "http://raspberrypi.local:8010/?1=0");
        	    xhr.send();

そもそもオリジンが違うと何でデータをダウンロード出来ないのか。それは、Web セキュリティ上の問題の様です。クライアントにデータを提供しているサーバー側からすれば、自分(オリジン)とは違うサーバーからデータをダウンロードされそれは結果自分の管理外の現象が起こる可能性が有り、それは避ける必要が有るからです。とはいえ今回の様に違うオリジンからデータをダウンロードしたい時はあるはずで、そうゆう時はどうすれば良いのか。

オリジン違いで値を返したい時

結論から言うとサーバーが Access-Control-Allow-Originヘッダーを返信すれば値を返せる様です。

Access-Control-Allow-Origin: *
Access-Control-Allow-Origin: <origin>

オリジンとして”*”を指定すれば全てのクライアントに、特定のオリジンのみ許可する時は<origin>にクライアントを指定します。

ここで疑問なんですが、例えばサーバが全てのリクエストに対してヘッダーとして、Access-Control-Allow-Origin: *を返せばクライアントはオリジンが違ってもデータをダウンロード出来ることになります。これでWeb セキュリティ上の問題が解決出来るんでしょうか。

ともあれ上記の様にPython WebServerにヘッダーを追加すればデータをダウンロード出来るようになりました。

確認アプリ

ここまでの動作を確認する為に簡単なアプリを書いてみました。アプリは、

  • Raspberry PI側
    • 電源を入れる。レディーになった時点でUV4Lは起動している。
    • その後 Python WebServerを起動する。このサーバーのポートは8010。
  • Notebook側
    • ”intercom.html”をFireFoxで上げる
    • このHPは 前回作成したHP画面に、”test”ボタンを追加したもの
    • この”test”ボタンをクリックすると、xhr.open(‘GET’, “http://raspberrypi.local:8010/?1=0”)が実行されRaspberry PIにGetリクエストが送られる。
  • リクエストを受けたRaspberry PI
    • リクエストを受けてモニターに、”Get Request”と表示。
    • クライアントに、”Data Back!”とデータを返す。
  • データを受けるNoteBook
    • データを受けて、コンソールに、”Data Back!”と表示

Raspberry PI 側にPython WebServerを追加しています。ファイル名は intercom.py です。

intercom.py

from http.server import HTTPServer, SimpleHTTPRequestHandler
from urllib.parse import urlparse,parse_qs
	
class MyHandler(SimpleHTTPRequestHandler):

    def do_GET(self):
        self.send_response(200)
        parsed = urlparse(self.path)
        params = parse_qs(parsed.query)
        a = next(iter(params))

        if a == "1":
            print('Get Request')

            self.send_header('Access-Control-Allow-Origin', '*')
            self.send_header('Content-type', "text/plain")
            self.end_headers()
            self.wfile.write("Data Back!".encode())
 
host = ''
port = 8010
httpd = HTTPServer((host, port), MyHandler)
print('serving at port', port)
httpd.serve_forever()
  • 6行:Python WebServerでは、Getリクエストは全てここで処理されます。
  • 8から10行: リクエストからクエリの取り出し
  • 12行: クエリのパラメータが、”1”で有ることを判断
  • 13行: モニターに”Get Request”と表示
  • 15行: ヘッダーに、Access-Control-Allow-Origin: * を指定
  • 18行: ”Data Back!”をクライアントに送信
  • 21行: 使用ポートを8010に指定。

NoteBook側は、前回作成したソフトを変更しています。変更したソフトは、”intercom.html”と”interfom.js”です。

“intercom.html”でHPに”Test”ボタンを追加。33行でHPにボタンを追加しています。ボタンをクリックすると、”test()”関数が実行されます。

intercom.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <link rel='stylesheet' type='text/css' href='./intercom.css' >
        <title>UV4L WebRTC</title>
    </head>

    <body>
        <div class='b_frame'>
   	        <div class='t_font'><u>UV4L WebRTC 1.0</u></div>

            <div class='p_frame'>
                <video id="remote-video" autoplay="" width="640" height="480"> </video> 
                <video id="play-video" controls style='display:none'>Your browser does not support the video tag.</video>
            </div>

            <div class="_menu">
               <div class="input-group"> 
                    <label>Stream</label>
       	            <button type='button' id='start' onclick='start();'>Start</button>
                    <button type='button' id="pause" onclick="pause();" title="pause or resume">Pause</button>
                    <button type='button' id="mute" onclick="mute();" title="mute or unmute ">Mute</button>
                    <button type='button' id='stop' onclick="stop();">Stop</button><br>
                    <label>Take & Play</label>
                    <button type='button' id='photo' onclick="take_photo();" title="take phato">Photo</button>
                    <button type='button' id="record" onclick="start_stop_record();" title="start or stop recording">Video</button>
                    <button type='button' id="play" onclick="play_video();" title="play video">Play</button><br>
                    <label>Download</label>
                    <button type='button' id='dn_photo' onclick="dn_photo();" title="save photo">Photo</button>
                    <button type='button' id='dn_video' onclick="download();" title="save video">Video</button><br>
                    <label>Test</label>
                    <button type='button' id='test' onclick="test();" title="save video">test</button>

                    <canvas style='display:none'></canvas>
                </div>
            </div>
        </div>

        <script src='intercom.js'></script>
    </body>
</html>

次は、”interfom.js”。”test()”関数を実行してGetリクエストを行います。下記は追加された関数、”test()”部分をのみを示しています。

intercom.js

            function test() {
    	        var xhr = new XMLHttpRequest();	
        	    xhr.open('GET', "http://raspberrypi.local:8010/?1=0");
        	    xhr.send();
        	    xhr.onreadystatechange = function() 
        	    {
        	        if(xhr.readyState === 4 && xhr.status === 200) 
         	            console.log( xhr.responseText );
                }
            }

//====================================================================================================================
            var ws = null;
            var pc;
            var audio_video_stream;
            var recorder = null;
            var aa_streams = [];
            var mediaConstraints = {
                optional: [],
  • 3行: ここでGetリクエストを行っています。
  • 5行: ここで、Python Serverからの返答を待っています。
  • 8行: コンソールに受け取ってデータを表示。

各ファイルの保存場所ですが、以下の様にしています。

  • Raspberry PI側の”intercom.py”
    • Raspberry PIのホームディレクトリの下に”intercom”というフォルダーを作りそこに保存。
  • NoteBook側の、”intercom/html”と”intercom.js”
    • NoteBookのホームディレクトリの下に”intercom”というフォルダーを作りそこに保存。
    • NoteBook側のファイルをここに保存します。ー>intercomダウンロード

実行

  • Raspberry PI側
    • 電源を入れてレディーになるのを待つ
    • intercom フォルダーに移動。
    • このフォルダーには、intercom.pyのみが保存されています。
    • ここで、python3 intercom.py とサーバーを立ち上げる。
  • NoteBook側
    • FireFoxを使って”intercom.html”を立ち上げる。
    • FireFoxの開発ツールを使ってHPの表示を変更。コンソールタブを選択。
    • HPの画面に有る”test”ボタンを押すと、”Data Back!”とサーバーからの値が表示されます。
  • この時Raspberry PI側のモニターには、クライアントからのGetリクエストを受けて”Get Request”と表示されます。

まとめ

ややこしい事になって来ましたが、NoteBookで挙げたHPからオリジンが違うRaspberry PIのPythonサーバーとデータのやり取りが出来る様になりました。次は、Pythonサーバーで写真、動画の留守録が出来る様にしたいと思います。