資料ページ Taking still photos with WebRTC 2016/3末時点のWebRTCブラウザ対応状況まとめ ※ デモページは、WEBアプリ用のテンプレートを Bootstrap を使用してスマホ対応で作成しています ▼ デモページ ※ この画像では、カメラが無い場合の代替の動画を使用しています MediaDevices - Web API インターフェイス | MDN Taking still photos with WebRTC - Web APIs | MDN ▼ 初回( Google Chrome ) ソースコード カメラを使うにしても、動画を使うにしても VIDEO 要素が使用されます。そこから、いったん canvas へコピーして、base64 で表現された画像に変換します。 アップロード時は、FormData を使い、データはバイナリに変換してアップロードします。(最大3枚までアップロード可能にしています) アップロードは ajax で行われるので、ページが書き換わる事はありません。結果として返される json は、$_FILES の内容をそのまま返しています(メッセージを追加しています)
<!DOCTYPE html> <html lang="ja"> <head> <meta content="width=device-width initial-scale=1.0 minimum-scale=1.0 maximum-scale=1.0 user-scalable=no" name="viewport"> <meta charset="utf-8"> <title>カメラ撮影とアップロード</title> <!-- jQuery --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> <!-- jQuery UI --> <link id="link" rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.10.1/themes/base/jquery-ui.css"> <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/css/bootstrap.css"> <!-- jQuery.mmenu --> <script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery.mmenu/5.5.3/core/js/jquery.mmenu.min.all.js"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jQuery.mmenu/5.5.3/core/css/jquery.mmenu.all.css"> <!-- toastr --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.3/toastr.min.css"> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.3/toastr.min.js"></script> <link rel="stylesheet" href="std/mmenu.css"> <link rel="stylesheet" href="std/basic.css"> <style> .fields { width: 85px; font-size: 12px; vertical-align: middle!important; } legend { font-size: 18px; padding-left: 6px; } /* 画像表示用 */ #row2 { vertical-align: top!important; } /* カメラ用 */ #camera { width: 400px; height: 300px; object-fit: fill; } #canvas { /* display: none; */ } </style> <script> jQuery.isMobile = (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase())); toastr.options={"closeButton":false,"debug":false,"newestOnTop":false,"progressBar":false,"positionClass":"toast-bottom-center","preventDuplicates":false,"onclick":null,"showDuration":"300","hideDuration":"1000","timeOut":"3000","extendedTimeOut":"1000","showEasing":"swing","hideEasing":"linear","showMethod":"fadeIn","hideMethod":"fadeOut"}; if ( !$.isMobile ) { toastr.options.positionClass = "toast-top-center"; } var datepicker_option = { dateFormat: 'yy/mm/dd', dayNamesMin: ['日', '月', '火', '水', '木', '金', '土'], monthNames: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], showMonthAfterYear: true, yearSuffix: '年', changeYear: true, showAnim: 'fadeIn', yearRange: "c-70:c" } $(function(){ var curlink = $("#title").text(); $("#title").html("<a href=\"./\" style=\"color:#fff\">" + curlink + "</a>"); }); var options = { row1 : { title : "カメラ" }, row2 : { title : "" }, row3 : { title : "画像一覧<br>(ロード順)" }, row4 : { title : "" }, row_last : { title :"メッセージ" }, error : function(message){ $("#row_last").next().text( message ); toastr.error(message); }, info : function(message){ $("#row_last").next().text( message ); toastr.success(message); }, cerror : function( message ){ message = message + "<br>代替として動画を表示します" $("#row_last").next().html( message ); toastr.error( message ); $("#camera") .prop({ "loop" : true, "muted" : true, "controls" : true, "src" : "mp4/freebies_018_win.mp4" }) .css("border", "solid 1px #000"); } }; // ************************************* // カメラ用データ // ************************************* var camera; var canvas; var copy_count = 0; $(function(){ // 1) options による行とフィールドの設定 // 2) Bootstrap 用 form-control クラスの追加 $(".fields").each(function(){ if ( options[ $(this).prop("id") ] ) { $(this).html( options[ $(this).prop("id") ].title ); // 個別 css if ( options[ $(this).prop("id") ].css ) { $(this).next().find("input,select").css( options[ $(this).prop("id") ].css ); } // 入力チェック用属性 if ( options[ $(this).prop("id") ].attr ) { $(this).next().find("input,select").attr( options[ $(this).prop("id") ].attr ); } } $(this).next().find("input,select").addClass("form-control"); }); // スマホでロード時の処理のチラつき防止用 $("#wrapper").css({"visibility":"visible", "margin-bottom" : "0px" }); // 初期フォーカス setTimeout( function(){$('#row1_fld').focus();}, 100 ); // video 内 camera camera = $("#camera").get(0); // ************************************* // 最新 API // ************************************* if ( navigator.mediaDevices ) { console.log("navigator.mediaDevices"); if ( $.isMobile ) { $("#camera").css("width","100%"); } navigator.mediaDevices.getUserMedia({video: true}) .then(function(stream){ camera.srcObject = stream; }) .catch(function(err){ // ブラウザで使用を拒否した場合等( 動画で代替 ) options.cerror(err.name); }); } // ************************************* // 旧 API // ************************************* else { console.log("navigator.getUserMedia"); // 旧 WebRTCチェック用 var api = [ "webkitGetUserMedia", "mozGetUserMedia","msGetUserMedia" ] $.each(api,function(idx){ if (navigator.getUserMedia = navigator.getUserMedia || navigator[api[idx]]) { return false; } }); // WebRTC 使用可能 if ( navigator.getUserMedia ) { if ( $.isMobile ) { $("#camera").css("width","100%"); } // カメラの表示 navigator.getUserMedia({video: true}, function(stream) { camera.src = window.URL.createObjectURL(stream); }, function(err){ // ブラウザで使用を拒否した場合等( 動画で代替 ) options.cerror(err.name); } ); } else { // WebRTC 使用不可( 動画で代替 ) options.cerror("WebRTC を使用できません"); } } // ************************************* // canvas にコピーして画像に変換 // ************************************* $("#copy").on( "click", function(){ copy_count++; if ( copy_count > 3 ) { options.error("撮影は3枚までです"); return false; } canvas = $("#canvas").get(0); var ctx = canvas.getContext('2d'); ctx.drawImage(camera, 0, 0, canvas.width, canvas.height); $("<img>").appendTo("#images") .prop( {"src": canvas.toDataURL("image/jpeg"), "id": "image"+ copy_count } ) .css( {"width": "100px", "margin": "10px" } ); }); // ************************************* // アップロード処理 // ************************************* $("#frm").submit( function(event){ // 本来の送信処理はキャンセル event.preventDefault(); if ( $("#images").html() == "" ) { options.error("アップロードする画像ファイルを作成して下さい"); return; } $("fieldset").eq(0).prop("disabled", true); // エラーメッセージエリアをクリア $(".error").next().text( "" ); // 結果の表示エリアを全てクリア $("#result").html( "" ); // ************************************** // ファイルのアップロード // ************************************** console.log("アップロード処理開始"); var formData = new FormData(); // テストの為、約100K の制限 formData.append("MAX_FILE_SIZE", 100000); var file_cnt = 0; $("#images img").each( function() { var base64 = $(this).prop("src"); var bin = atob(base64.split(',')[1]); var buffer = new Uint8Array(bin.length); for (var i = 0; i < bin.length; i++) { buffer[i] = bin.charCodeAt(i); } var blob = new Blob([buffer.buffer], {type: "image/jpeg"}); file_cnt++; var file_name = (new Date()).getTime(); formData.append("image"+file_cnt, blob, file_name +"_"+file_cnt+".jpg"); }); formData.append("FILE_COUNT", file_cnt ); $.ajax({ url: "./upload.php", type: "POST", data: formData, processData: false, // jQuery がデータを処理しないよう指定 contentType: false // jQuery が contentType を設定しないよう指定 }) .done(function( data, textStatus ){ console.log( "status:" + textStatus ); console.log( "data:" + JSON.stringify(data, null, " ") ); options.info("アップロード処理が完了しました"); // アップロード結果の表示 $.each(data, function(idx,image){ if ( image.error != 0 ) { $("#result").append("<tr><td><span id=\"result" +idx+"\"></span><b style='color:red'>" + image.name+ " : " + image.result +"</b></td></tr>"); } else { $("#result").append("<tr><td><span id=\"result" +idx+"\"></span>" + image.name + " : " + image.result +"</td></tr>"); } $( "#result"+idx ).append($("#"+idx).clone()); }); $("#images").html(""); copy_count = 0; }) .fail(function(jqXHR, textStatus, errorThrown ){ console.log( "status:" + textStatus ); console.log( "errorThrown:" + errorThrown ); options.info("アップロードに失敗しました"); }) .always(function() { // 操作不可を解除 $("fieldset").eq(0).prop("disabled", false); }) ; } ); // ************************************** // mmenu // ************************************** $("#mmenu_left").mmenu({ navbar: { title: "メニュー" }, offCanvas: { position : "left", zposition : "next" } }); }); </script> </head> <body> <div id="wrapper"> <script> // スマホでロード時等の処理のチラつき防止用 $("#wrapper").css( {"visibility": "hidden", "margin-bottom" : "1000px" } ); </script> <div id="head"> <a id="hamburger" href="#mmenu_left"> <span class="top-bar"></span> <span class="middle-bar"></span> <span class="bottom-bar"></span> </a> <div id="title">カメラ撮影とアップロード</div> </div> <div id="body"> <form id="frm" class="form-inline"> <fieldset> <legend>アップロード</legend> <table class="table table-condensed"> <tr> <td class="fields" id="row1"></td> <td> <video id="camera" autoplay></video> <canvas id="canvas" width="400" height="300"></canvas> </td> </tr> <tr> <td class="fields" id="row2"></td> <td> <input id="copy" type="button" class="btn btn-primary btn-sm" value="撮影"> </td> </tr> <tr> <td class="fields" id="row3"></td> <td> <div id="images"></div> </td> </tr> <tr> <td class="fields" id="row4"></td> <td> <input id="action" type="submit" class="btn btn-primary btn-sm" value="送信"> </td> </tr> <tr> <td class="fields error" id="row_last"></td> <td></td> </tr> </table> </fieldset> <fieldset> <legend>結果</legend> <table id="result" class="table table-condensed"> </table> </fieldset> </form> </div> <div id="comment"> ようこそ jQuery + Bootstrap(css) + mmenu + WebRTC(カメラ) + FormData + PHP<br><a href="https://www.studio-lab01.com/freebies.html" target="_blank">素材提供:らぼわん</a> / カメラが無い場合の動画素材 </div> </div> <div id="mmenu_left"> <ul> <li class="mm_user_title">ページ選択</li> <li><a class="mm_link_left" href="#" onclick="location='index.php';void(0)">リセット</a></li> <li><a class="mm_link_left" href="http://getbootstrap.com/css/" onclick="location='index.php';void(0)" target="_blank" >Bootstrap(css)</a></li> <li><a class="mm_link_left" href="http://api.jquery.com/" onclick="location='index.php';void(0)" target="_blank" >jQuery ドキュメント</a></li> <li><a class="mm_link_left" href="https://developer.mozilla.org/ja/docs/Web/Guide/Using_FormData_Objects" onclick="location='index.php';void(0)" target="_blank" >FormData オブジェクトの利用 / MDN</a></li> <li><a class="mm_link_left" href="https://developer.mozilla.org/ja/docs/Web/API/MediaDevices" onclick="location='index.php';void(0)" target="_blank" >MediaDevices (MDN)</a></li> <li><a class="mm_link_left" href="https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Taking_still_photos" onclick="location='index.php';void(0)" target="_blank" >Taking still photos with WebRTC(英文)</a></li> </ul> </div> </body> </html>
upload.php ※ アップロードした画像の保存部分はコメントにしています
<?php // キャラクタセット // ************************************* header( "Content-Type: application/json; charset=utf-8" ); // ************************************* // キャッシュ無効 // ************************************* session_cache_limiter('nocache'); session_start(); // ファイルを移動するフォルダ $target_folder = "./images/"; $cnt = $_POST["FILE_COUNT"] + 0; for( $i = 0; $i < $cnt; $i++ ) { $image_target = "image".($i+1); if ( $_FILES[$image_target]["error"] == 0 ) { // ************************************* // 1) 画像フォーマットの取得 // ************************************* $type_string = image_type_to_mime_type( exif_imagetype( $_FILES[$image_target]['tmp_name'] ) ); // ************************************* // 2) オリジナルファイル名の取得 // ************************************* $file = explode(".", $_FILES[$image_target]['name']); // ************************************* // 3) 日本語ファイル名対応 // ************************************* $file_name = urlencode( $file[0] ); // ************************************* // 4) 保存ファイル名を作成 // a) 拡張子決定 // b) uniqid() でファイル目をユニーク // ************************************* $target = ""; if ( $type_string == "image/jpeg" ) { $target = uniqid() . "_{$file_name}.jpg"; } if ( $type_string == "image/gif" ) { $target = uniqid() . "_{$file_name}.gif"; } if ( $type_string == "image/png" ) { $target = uniqid() . "_{$file_name}.png"; } if ( $target == "" ) { $_FILES["image"]["result"][] = "アップロードできないフォーマットです"; } else { // ************************************* // アップロードファイルの保存 // ************************************* // if ( @move_uploaded_file( $_FILES[$image_target]['tmp_name'], $target_folder . $target ) ) { $_FILES[$image_target]["result"] = "アップロードに成功しました"; // } // else { // なんらかの環境エラー // $_FILES[$image_target]["result"] = "アップロードに失敗しました"; // } } } else { switch($_FILES[$image_target]["error"]){ case 1: $_FILES[$image_target]["result"] = "php.ini の upload_max_filesize ディレクティブの値を超えています"; break; case 2: $_FILES[$image_target]["result"] = "HTML フォームで指定された MAX_FILE_SIZE を超えています"; break; case 3: $_FILES[$image_target]["result"] = "一部のみしかアップロードされていません"; break; case 4: $_FILES[$image_target]["result"] = "アップロードされませんでした"; break; case 6: $_FILES[$image_target]["result"] = "テンポラリフォルダがありません"; break; case 7: $_FILES[$image_target]["result"] = "ディスクへの書き込みに失敗しました"; break; case 8: $_FILES[$image_target]["result"] = "PHP の拡張モジュールがファイルのアップロードを中止しました"; break; default: $_FILES[$image_target]["result"] = "不明なエラーです"; } } } print json_encode($_FILES) ?>
関連する記事 WebRTC による WEBカメラ表示
|
【APIの最新記事】
- WebRTC による WEBカメラ表示( 2018/11/15 ) : iPhone + Safari は iOS11 のみで確認
- PHP で Mastodon にアプリを登録して投稿する手順
- Google スプレッドシートの内容を JSON として localhost で取得する手順
- WebRTC による WEBカメラ表示
- Amazon API の 503エラー の対処について
- ドコモ デベロッパーサポートで API使いたいと思ってサインアップしようとしたら・・・・。法人情報は後から登録しましょう
- GitHub の yahoojapan/yconnect-php-sdk( PHP ) を使用して、Yahoo! にログインさせてユーザ情報を取得する( OPENID )
- 手動で Facebook API の 60日間の アクセストークンを取得する
- Facebook Graph API Explorer でカスタムプライバシー設定をした投稿を行う
- C#(.NET) : Google Spreadsheets API version 3.0でGoogleスプレッドシートを参照
- Twitpic の画像URLを取得する API が変わっているようなのですが