gates1deの備忘録

主に開発でひっかかったこと, ハマったことを記録します

ラズパイつくった

学生エンジニア Advent Calendar 2015の14日目です

あらまし

この1年, 研究開発の関係で3軸加速度センサ+3軸角速度センサ+Bluetoothモジュールを搭載したマイコンを使ってゴニョゴニョしてました.

自分はハードウェアに関してはあまり開発経験がないもので, これは先生が作ったものです.

しかし, 内部のプログラムも動作仕様も不明な状態で始めたので, 手探りしながらBluetooth接続, 信号送信, センサデータの取得などをおこなってました.

満足してない部分もあったので, そのうちに「自分で作れたらなー」なんて思いながら, マイコンについて調べてました.

(その矢先, Raspberry Pi Zeroが発売されたということで即買いました.)



どうせなら, Raspberry Piみたいになんでもくっつけれそうなやつ作ってみたいなぁと.




Raspberry Piみたいな...




ラズペリーPi...






ラズベリーパイ作りたい」





ということでラズベリーパイを作りました. (以下作り方)

材料・準備するもの

パイ生地

  • 薄力小麦粉: 100g
  • 強力小麦粉: 100g
  • バター :165g
  • 水: 80ml

ベリーコンポート

  • 冷凍ラズベリー: 300g
  • 冷凍ブルーベリー: 150g
  • 砂糖: 100g
  • リキュール: 適量
  • レモン果汁: 適量

カスタードクリーム

  • 卵: 2個
  • 薄力小麦粉: 30g
  • 牛乳: 400ml
  • 砂糖: 90g

飾り

ナパージュ

  • 水: 75ml
  • 砂糖: 30g
  • 粉ゼラチン: 5g

作り方

パイ生地

自分のお菓子作りライフの中では, スポンジ生地とタルト生地は作ったことはあって,
まだパイ生地を作ったことはありませんでした.
Raspberry Piで言うところの基盤部分です. 大事ですね.

※ 参考はこちら. 参考に書いてないところを書いていきます.

1. 薄力粉と強力粉は振るわなくてもいいですがよく混ぜておいたほうが良いと思います
2. バターを切りながらすり混ぜる際に, カードや固いヘラがなければしゃもじで十分です
f:id:gateside:20151213165039j:plain:w326:h244
3. 水は3回くらいに分けて混ぜると良いです
水混ぜ後と成形後
f:id:gateside:20151213165054j:plain:w216:h162 f:id:gateside:20151213165106j:plain:w216:h162
4. 形はとりあえずなんでもいいので生地を寝かしておきます
f:id:gateside:20151213165117j:plain:w326:h244
5. あとは自分の好きなだけ, 伸ばして・2回折って, を繰り返します
f:id:gateside:20151213165129j:plain:w162:h216f:id:gateside:20151213165141j:plain:w162:h216
6. 自分はホールの型に合わせるため, 円形に伸ばしました
f:id:gateside:20151213165152j:plain:w244:h326
7. ホールに入れて形を整え, 余分なところを切ります
f:id:gateside:20151213165203j:plain:w162:h216f:id:gateside:20151213165214j:plain:w162:h216
8. ピケ(フォークで穴あけ)して卵黄を塗ります(中には塗らなくて良いです失敗しました)
f:id:gateside:20151213165225j:plain:w162:h216f:id:gateside:20151213165236j:plain:w162:h216
9. 普通なら重石をのせて焼くのですが, ない人が多いと思うので米か小豆か小石を使いましょう
f:id:gateside:20151213165247j:plain:w244:h326
10. 210℃に余熱したオーブンで, 200℃で20分焼きます(実際は中のほうが焼けてない感じなので, 15分くらい焼いて, 重石ははずして5分くらい焼いたほうがいいかもしれません)
f:id:gateside:20151213165310j:plain:w244:h326
11. 出来上がり! 冷ましておきましょう
f:id:gateside:20151213165333j:plain:w162:h216f:id:gateside:20151213165344j:plain:w162:h216

ベリーコンポート

以前, りんごのコンポートは作ったことがあるのでそれを頼りに適当に作ってみました.
ラズベリーとブルーベリーだけでも結構酸味があるのですが, カスタードクリームと一緒に
すれば甘みと酸味のハーモニーが味わえるはずです. ハズです.
リキュールは単純に柑橘系が好きなのでオレンジキュラソーをいれてます.
レモン果汁も入れちゃったけど入れなくてもいいと思います. ビタミンが欲しかったんですねぇ.

1. 材料を全部つっこんで混ぜる(ある程度潰してもおkです)
f:id:gateside:20151213165258j:plain:w244:h326
2. 8~10分くらい中火で煮込む
3. 出来上がると思います! 冷やしておきましょう
結構自分は潰しちゃったんで果汁が出過ぎちゃいましたかね
f:id:gateside:20151213165320j:plain:w244:h326

カスタードクリーム

クリームパンのクリームっておいしいですよね.
それが簡単に作れるなんて夢にも思ってませんでした.
ということで作り方は超簡単です.

1. 鍋に材料を全部混ぜる(小麦粉はふるったほうが良いです)
2. 混ぜながら強火にかけて, とろみがでてきたら即弱火にするか火を止めます
3. 出来上がり!
f:id:gateside:20151213165358j:plain:w244:h326

ナパージュ

ナパージュは, よくスイーツで使われるフルーツに塗ってあるテカテカしたやつです.
これは省略レベルです.
※ 仕上げの工程2の後に作ってください

1. 水と砂糖を入れてレンチン1分
2. ゼラチンいれて混ぜて冷ましておく(冷ましすぎ注意)
3. 出来上がり!

仕上げ

それぞれができたところで, 全部合わせていきます.

1. パイ生地にカスタードを流したら, ちょっと冷やす
f:id:gateside:20151213165409j:plain:w244:h326
2. 続いてベリーコンポートを流したら, 一晩冷やす
f:id:gateside:20151213165423j:plain:w244:h326
3. 生ラズベリーにナパージュを塗って盛り付け, ミントをのせるとハイ出来上がり!
f:id:gateside:20151213165508j:plain:w162:h216f:id:gateside:20151213165521j:plain:w162:h216

包丁がカスみたいな包丁だったんで, 切り口は微妙ですが切った感じはこんなんです.
f:id:gateside:20151213165534j:plain:w244:h326


感想

パイ生地が, 中はもちもち外はサクサクで, カスタードの甘さとベリーの酸味がとても良くあったラズベリーパイになりました.
この調子でRaspberry Piみたいなのも作れたらいいんですけどね! 無理か

あとがき

1週間くらい前に届いたRaspberry Pi Zeroをようやくいじり始めました.
とりあえず初期設定を済ませて, 今財布をなくしがちな人(?)のためのIoTデバイスを作ってます.
本当は間に合わせれたらよかったんですけどねーすみません.


それではこの辺で〜〜〜〜〜〜

jQuery Mobileでビデオチャット

前回の記事に引き続き, WebRTCを利用したアプリということでビデオチャットを例に色々と学習しています.
今回は, AndroidアプリでWebRTC実現できないかなーと思って, ブラウザをそのままAndroidアプリに貼り付ける「WebView」というものを使って実装してみよう! …としてみましたが, どうやらAndroid4.4(現時点で最新)ですらWebViewのブラウザ(Androidの既定ブラウザ)がgetUserMedia(カメラやマイクを使った動画・音声の取得)に対応していないため諦めました.
でもやっぱりユーザビリティを考えると, スマートフォンからWebアプリにアクセスするのは面倒かつ見た目が非常に良くないので, jQuery Mobileでネイティブライクなものを作ってみようと思いつきました.
まぁそれでもWebアプリにアクセスすることは変わりないので, お気に入りとかに入れたアプリへのリンクを開かなきゃいけないのは面倒だと思ったので, Androidアプリから直接ブラウザでアプリを開いて, その後アプリを終了させるという無駄なことをしでかしました.
手間が減って少し楽になるってのもあるけど, Google Cloud Message(Push通知)とかも使えちゃうのでまぁ無駄でもないかなとプラスに考えてます笑
前置きが長くなりましたが, そんな感じでやっていきましょう.

前回のビデオチャットの編集

前回はcssとかも使っていないし, ビデオチャットを使う人にとっていらない部分も見えていたので, まだパソコンで見るならまだしも, スマホでアクセスされるとちょいと厳しいものがありました.
ということで前回のvideoChat.htmlを以下のように編集してみましょう(新しくファイルとして作って頂いて構いません).

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>jQuery Mobile</title>
<link rel="stylesheet"
  href="http://code.jquery.com/mobile/1.4.0/jquery.mobile-1.4.0.min.css" />
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="http://code.jquery.com/mobile/1.4.0/jquery.mobile-1.4.0.min.js">
</script>
</head>
<body>

<!--ページ領域-->
<div data-role="page" data-title="jQuery Mobile Video Chat">

  <!--ヘッダー領域-->
  <div data-role="header">
    <h1>jQuery Mobile Video Chat</h1>
  </div>

  <div role="main" class="ui-content">
		<div data-role="controlgroup" data-type="horizontal" data-inline="true" data-mini="true">
			<button class="ui-btn" onclick="startVideo();">Video ON</button>
			<button class="ui-btn" onclick="stopVideo();">Video OFF</button>
		  <button class="ui-btn" onclick="connect();">Connect</button>
		  <button class="ui-btn" onclick="exit();">Exit</button>
		</div>  
		<br />
		<div data-role="content">
			<video id="local_video" autoplay style="width: 300px; height: 225px; border: 1px solid black;"></video>
			<video id="remote_video" autoplay style="width: 300px; height: 225px; border: 1px solid red;"></video>
		</div>
	</div>
  <div data-role="footer">
    <h3>Copyright 2014-201X, Yyy.Xxxxx</h3>
  </div>

</div>
<script src="/socket.io/socket.io.js"></script>
  
<script>
  var localVideo = document.getElementById('local_video');
  var remoteVideo = document.getElementById('remote_video');
  var localStream = null;
  var peerConnection = null;
  var peerStarted = false;
  var mediaConstraints = {'mandatory': {'OfferToReceiveAudio':true , 'OfferToReceiveVideo':true }};
  navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
  
  var socketReady = false;
  var socket = io.connect('/');

	socket.on('connect', onOpened)
        .on('message', onMessage);

  function onOpened(event) {
    console.log('socket opened.');
    socketReady = true;
  }

  function onMessage(event) {
    if (event.type === 'offer') {
      console.log("Received offer, set offer, sending answer....")
      onOffer(event);	  
    } else if (event.type === 'answer' && peerStarted) {
      console.log('Received answer, settinng answer SDP');
	  onAnswer(event);
    } else if (event.type === 'candidate' && peerStarted) {
      console.log('Received ICE candidate...');
	  onCandidate(event);
    } else if (event.type === 'user dissconnected' && peerStarted) {
      console.log("disconnected");
      stop();
    }
  }
  
	var SendSDPValue;
	var SendICEValue;
	var ReceiveSDPValue;
	var ReceiveICEValue;
	var SendCandidateText;
	var CR = String.fromCharCode(13);
  
  function onSDP() {
    var text = SendSDPValue;
		var event = JSON.parse(text);
		if (peerConnection) {
		  onAnswer(event);
		}
		else {
		  onOffer(event);
		}

		ReceiveSDPValue ="";
  }  
  
  function onICE() {
		var text = SendCandidateText;
		var event = JSON.parse(text);
		onCandidate(event);
		ReceiveICEValue ="";
  }
  
  
  function onOffer(event) {
    console.log("Received offer...")
		console.log(event);
    setOffer(event);
		sendAnswer(event);
		peerStarted = true;
  }
  
  function onAnswer(event) {
    console.log("Received Answer...")
		console.log(event);
		setAnswer(event);
  }
  
  function onCandidate(event) {
    var candidate = new RTCIceCandidate({sdpMLineIndex:event.sdpMLineIndex, sdpMid:event.sdpMid, candidate:event.candidate});
    console.log("Received Candidate...")
		console.log(candidate);
    peerConnection.addIceCandidate(candidate);
  }

  function sendSDP(sdp) {
    var text = JSON.stringify(sdp);
		console.log("---sending sdp text ---");
		console.log(text);
		SendSDPValue = text;

		socket.json.send(sdp);
  }
  
  function sendCandidate(candidate) {
		SendCandidateText = JSON.stringify(candidate);
		console.log("---sending candidate text ---");
		console.log(SendCandidateText);
		socket.json.send(candidate);
  }
  
  function startVideo() {
		navigator.getUserMedia({video: true, audio: true},
			function (stream) {
				localStream = stream;
	      localVideo.src = window.webkitURL.createObjectURL(stream);
				localVideo.play();
		 // localVideo.volume = 0;
			},
			function (error) {
				console.error('An error occurred: [CODE ' + error.code + ']');
				return;
			}
		);
	}

  function stopVideo() {
    localVideo.src = "";
    localStream.stop();
  }

  function prepareNewConnection() {
    var pc_config = {"iceServers":[
     {"url":"stun:220.110.149.251:3478"},
     {"url":"turn:220.110.149.251:3478", "username":"push", "credential":"push0077"}
    ]};
    var peer = null;
    try {
      peer = new webkitRTCPeerConnection(pc_config);
    } catch (e) {
      console.log("Failed to create peerConnection, exception: " + e.message);
    }

    peer.onicecandidate = function (event) {
      if (event.candidate) {
        console.log(event.candidate);
        sendCandidate({type: "candidate", 
                          sdpMLineIndex: event.candidate.sdpMLineIndex,
                          sdpMid: event.candidate.sdpMid,
                          candidate: event.candidate.candidate}
		);
      } else {
        console.log("End of candidates. ------------------- phase=" + event.eventPhase);
      }
    };

    console.log('Adding local stream...');
    peer.addStream(localStream);

    peer.addEventListener("addstream", onRemoteStreamAdded, false);
    peer.addEventListener("removestream", onRemoteStreamRemoved, false)

    function onRemoteStreamAdded(event) {
      console.log("Added remote stream");
      remoteVideo.src = window.webkitURL.createObjectURL(event.stream);
    }

    function onRemoteStreamRemoved(event) {
      console.log("Remove remote stream");
      remoteVideo.src = "";
    }

    return peer;
  }

  function sendOffer() {
    peerConnection = prepareNewConnection();
    peerConnection.createOffer(function (sessionDescription) {
      peerConnection.setLocalDescription(sessionDescription);
      console.log("Sending: SDP");
      console.log(sessionDescription);
      sendSDP(sessionDescription);
    }, function () {
      console.log("Create Offer failed");
    }, mediaConstraints);
  }

  function setOffer(event) {
    if (peerConnection) {
	  console.error('peerConnection alreay exist!');
	}
    peerConnection = prepareNewConnection();
    peerConnection.setRemoteDescription(new RTCSessionDescription(event));
  }
  
  function sendAnswer(event) {
    console.log('sending Answer. Creating remote session description...' );
	if (! peerConnection) {
	  console.error('peerConnection NOT exist!');
	  return;
	}

    peerConnection.createAnswer(function (sessionDescription) {
      peerConnection.setLocalDescription(sessionDescription);
      console.log("Sending: SDP");
      console.log(sessionDescription);
      sendSDP(sessionDescription);
		}, function () { 
			console.log("Create Answer failed");
    }, mediaConstraints);
  }

  function setAnswer(event) {
    if (! peerConnection) {
	  console.error('peerConnection NOT exist!');
	  return;
	}
	peerConnection.setRemoteDescription(new RTCSessionDescription(event));
  }
  
  function connect() {
		if (!peerStarted && localStream && socketReady) { 
			sendOffer();
      peerStarted = true;
    } else {
      alert("接続エラー: 相手がいないか, ビデオが起動していないか, インターネット接続に問題があります.");
    }
  }

  function exit() {
    console.log("Exit.");
    stop();
  }

  function stop() {
    peerConnection.close();
    peerConnection = null;
    peerStarted = false;
  }
</script>
</body>
</html>

300行くらいのコードですが, 前回と比べて200行くらい減らしました.
( ×jQuery Mobileだからコードが減った : いらないものを消した)

特にインストールするライブラリとかなくて, jQuery Mobileのスタイルシートへのリンクを指定してあげるだけで簡単に使えるのが魅力的ですね.
それでは簡単でしたが以上です.
Say good-bye!

herokuにビデオチャットをデプロイしてみた

やっと夏休みに入って一段落したので記事を(連続で)書いていこうかと思います.

最近はインターンの書類選考や面接等を受けているんですが, 現在インターン中でもあります.
インターン先ではWebRTCを使ってアプリを開発し, サーバと連携させるというのがテーマになっています.
その一つの例としてビデオチャットを作ろうということになりました.

この記事以降で紹介しますが, Android4.4(現時点で最新)のWebViewでは, カメラやマイクを使って自分と相手の動画・音声といったメディアを取得できないので, Webアプリとして実装しました.

話は少し飛びましたがherokuでシグナリングサーバを立ててWebRTCを実現させる方法を紹介していきます.

※インストールなどはMac OS X(Mavericks)で行っています. また, WebRTCの仕組みはここなどを参照してください.

それでは本編に入ります.

Node.jsのインストール

まずはNode.jsをインストールする必要があります.
インストールしていない方は以下の手順でインストールしてください.

$ git clone git://github.com/creationix/nvm.git ~/.nvm
$ source ~/.nvm/nvm.sh
$ nvm install 0.10.26

インストールされたか確認するために$ node -v とかやってみて「v0.10.26」と表示されたらNode.jsのインストールは完了です.

nvm(Node Version Manager)の設定

nvmを次回のターミナル起動時にもいつも通り使えるようにする設定です.
以下の手順になります.

$ nvm alias default v0.10.26
$ vim ~/.bash_profile
# 以下を追記
if [[ -s ~/.nvm/nvm.sh ]];
  then source ~/.nvm/nvm.sh
fi

npm(Node Package Manager)のインストールと設定

ノードのパッケージマネージャをインストール&設定します.
以下の手順で行ってください.

$ curl http://npmjs.org/install.sh | sh

どのディレクトリでも使えるように以下の手順を行い, パスを通します.

$ vim ~/.bash_profile
# 以下を追記
export PATH=$HOME/.npm/bin:$PATH
export NODE_PATH=$HOME/.npm/libraries:$NODE_PATH
export MANPATH=$HOME/.npm/man:$MANPATH

これも確認のために「npm -v」をやってインストールされたか確認してみてください.

expressインストール

expressはNode.jsのWebアプリを開発するためのフレームワークです.
まずは以下の手順でexpressをnpmでインストールしましょう.

$ npm install -g express-generator

Webアプリを自動生成する

次はexpressを使ってWebアプリを自動生成してみましょう.
以下の手順でやっていきます.

$ express video_chat
$ cd video_chat && npm install
$ npm install socket.io

あとうろ覚えで申し訳ないのですが, socket.ioもnpmを使ってインストールしないといけないので, 最後の行もやっておいてください.
これで一応Node.jsのWebアプリは出来たことになります.
とりあえずvideo_chatディレクトリに移動してファイルを見てみると, いくつか用意されていると思います.
次以降はビデオチャットのプログラムを書いていきましょう.

app.jsの編集

WebRTCを実現するためにシグナリングサーバを用意しなければなりません. その役割をapp.jsにやってもらいます.
app.jsを以下のように記述してみましょう.

var http = require('http');
var express = require('express');
var port = process.env.PORT || 3002;
var app = express();

app.get('/', function(req, res) {
  res.sendfile(__dirname + '/videoChat.html'); // 次に作るファイルへのパスです.
});
app.set('port', port);

var server = http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});
var io = require('socket.io').listen(server);
console.log((new Date()) + " Server is listening on port " + port);
 
io.sockets.on('connection', function(socket) {
  socket.on('message', function(message) {
    socket.broadcast.emit('message', message);
  });
    
  socket.on('disconnect', function() {
    socket.broadcast.emit('user disconnected');
  });
});

保存して終了します.
何故かrequire('http')してるんですが, どうやら必要ないっぽい…
これでも一応動作してるので自分はこんなかんじにしてますが, こういったソースコードはどこにでもありますので探してみてください(;´Д`)

htmlの作成

見た目の部分となるhtmlファイルを作成します.
上のプログラムの7行目にvideoChat.htmlと書いていますが, 今回はこのファイルを作ることにします.
htmlファイルの名前は自由ですが, signaling.jsの中身もかえてください.
プログラムはここを参考にさせて頂いてます.

<!DOCTYPE html>
<html>
<head>
  <title>WebRTC 1 to 1 signaling</title>  
</head>
<body>
  <button type="button" onclick="startVideo();">Start video</button>
  <button type="button" onclick="stopVideo();">Stop video</button>
  &nbsp;&nbsp;&nbsp;&nbsp;
  <button type="button" onclick="connect();">Connect</button>
  <button type="button" onclick="hangUp();">Hang Up</button>
  <br />
  <div>
   <video id="local-video" autoplay style="width: 240px; height: 180px; border: 1px solid black;"></video>
   <video id="remote-video" autoplay style="width: 240px; height: 180px; border: 1px solid black;"></video>
  </div>
  
  <p>
   SDP to send:<br />
   <textarea id="text-for-send-sdp" rows="5" cols="100" disabled="1">SDP to send</textarea>
  </p>
  <p>
   SDP to receive:<br />
   <textarea id="text-for-receive-sdp" rows="5" cols="100"></textarea><br />
   <button type="button" onclick="onSDP();">Receive SDP</button>
  </p>
  
  <p>
   ICE Candidate to send:<br />
   <textarea id="text-for-send-ice" rows="5" cols="100" disabled="1">ICE Candidate to send</textarea>
  </p>
  <p>  
   ICE Candidates to receive:<br />
   <textarea id="text-for-receive-ice" rows="5" cols="100"></textarea><br />
   <button type="button" onclick="onICE();">Receive ICE Candidates</button>
  </p>
  
  <!---- socket ------>
  <script src="/socket.io/socket.io.js"></script>
  
  <script>
  var localVideo = document.getElementById('local-video');
  var remoteVideo = document.getElementById('remote-video');
  var localStream = null;
  var peerConnection = null;
  var peerStarted = false;
  var mediaConstraints = {'mandatory': {'OfferToReceiveAudio':false, 'OfferToReceiveVideo':true }};

  
  // ---- socket ------
  // create socket
  var socketReady = false;
  var port = process.env.PORT || 3002;
  var socket = io.connect('/');
  // socket: channel connected
  socket.on('connect', onOpened)
        .on('message', onMessage);

  function onOpened(evt) {
    console.log('socket opened.');
    socketReady = true;
  }

  // socket: accept connection request
  function onMessage(evt) {
    if (evt.type === 'offer') {
      console.log("Received offer, set offer, sending answer....")
      onOffer(evt);	  
    } else if (evt.type === 'answer' && peerStarted) {
      console.log('Received answer, settinng answer SDP');
	  onAnswer(evt);
    } else if (evt.type === 'candidate' && peerStarted) {
      console.log('Received ICE candidate...');
	  onCandidate(evt);
    } else if (evt.type === 'user dissconnected' && peerStarted) {
      console.log("disconnected");
      stop();
    }
  }

  
  
  // ----------------- handshake --------------
  var textForSendSDP = document.getElementById('text-for-send-sdp');
  var textForSendICE = document.getElementById('text-for-send-ice');
  var textToReceiveSDP = document.getElementById('text-for-receive-sdp');
  var textToReceiveICE = document.getElementById('text-for-receive-ice');
  var iceSeparator = '------ ICE Candidate -------';
  var CR = String.fromCharCode(13);
  
  function onSDP() {
    var text = textToReceiveSDP.value;
	var evt = JSON.parse(text);
	if (peerConnection) {
	  onAnswer(evt);
	}
	else {
	  onOffer(evt);
	}
	
	textToReceiveSDP.value ="";
  }  
  
  //--- multi ICE candidate ---
  function onICE() {
    var text = textToReceiveICE.value;
	var arr = text.split(iceSeparator);
	for (var i = 1, len = arr.length; i < len; i++) {
      var evt = JSON.parse(arr[i]);
	  onCandidate(evt);
    }

	textToReceiveICE.value ="";
  }
  
  
  function onOffer(evt) {
    console.log("Received offer...")
	console.log(evt);
    setOffer(evt);
	sendAnswer(evt);
	peerStarted = true;  // ++
  }
  
  function onAnswer(evt) {
    console.log("Received Answer...")
	console.log(evt);
	setAnswer(evt);
  }
  
  function onCandidate(evt) {
    var candidate = new RTCIceCandidate({sdpMLineIndex:evt.sdpMLineIndex, sdpMid:evt.sdpMid, candidate:evt.candidate});
    console.log("Received Candidate...")
	console.log(candidate);
    peerConnection.addIceCandidate(candidate);
  }

  function sendSDP(sdp) {
    var text = JSON.stringify(sdp);
	console.log("---sending sdp text ---");
	console.log(text);
	textForSendSDP.value = text;
	
	// send via socket
	socket.json.send(sdp);
  }
  
  function sendCandidate(candidate) {
    var text = JSON.stringify(candidate);
	console.log("---sending candidate text ---");
	console.log(text);
	textForSendICE.value = (textForSendICE.value + CR + iceSeparator + CR + text + CR);
	textForSendICE.scrollTop = textForSendICE.scrollHeight;
	
	// send via socket
	socket.json.send(candidate);
  }
  
  // ---------------------- video handling -----------------------
  // start local video
  function startVideo() {
	navigator.webkitGetUserMedia({video: true, audio: false},
    function (stream) { // success
      localStream = stream;
      localVideo.src = window.webkitURL.createObjectURL(stream);
      localVideo.play();
	  localVideo.volume = 0;
    },
    function (error) { // error
      console.error('An error occurred: [CODE ' + error.code + ']');
      return;
    }
	);
  }

  // stop local video
  function stopVideo() {
    localVideo.src = "";
    localStream.stop();
  }

  // ---------------------- connection handling -----------------------
  function prepareNewConnection() {
    var pc_config = {"iceServers":[]};
    var peer = null;
    try {
      peer = new webkitRTCPeerConnection(pc_config);
    } catch (e) {
      console.log("Failed to create peerConnection, exception: " + e.message);
    }

    // send any ice candidates to the other peer
    peer.onicecandidate = function (evt) {
      if (evt.candidate) {
        console.log(evt.candidate);
        sendCandidate({type: "candidate", 
                          sdpMLineIndex: evt.candidate.sdpMLineIndex,
                          sdpMid: evt.candidate.sdpMid,
                          candidate: evt.candidate.candidate}
		);
      } else {
        console.log("End of candidates. ------------------- phase=" + evt.eventPhase);
      }
    };

    console.log('Adding local stream...');
    peer.addStream(localStream);

    peer.addEventListener("addstream", onRemoteStreamAdded, false);
    peer.addEventListener("removestream", onRemoteStreamRemoved, false)

    // when remote adds a stream, hand it on to the local video element
    function onRemoteStreamAdded(event) {
      console.log("Added remote stream");
      remoteVideo.src = window.webkitURL.createObjectURL(event.stream);
    }

    // when remote removes a stream, remove it from the local video element
    function onRemoteStreamRemoved(event) {
      console.log("Remove remote stream");
      remoteVideo.src = "";
    }

    return peer;
  }

  function sendOffer() {
    peerConnection = prepareNewConnection();
    peerConnection.createOffer(function (sessionDescription) { // in case of success
      peerConnection.setLocalDescription(sessionDescription);
      console.log("Sending: SDP");
      console.log(sessionDescription);
      sendSDP(sessionDescription);
    }, function () { // in case of error
      console.log("Create Offer failed");
    }, mediaConstraints);
  }

  function setOffer(evt) {
    if (peerConnection) {
	  console.error('peerConnection alreay exist!');
	}
    peerConnection = prepareNewConnection();
    peerConnection.setRemoteDescription(new RTCSessionDescription(evt));
  }
  
  function sendAnswer(evt) {
    console.log('sending Answer. Creating remote session description...' );
	if (! peerConnection) {
	  console.error('peerConnection NOT exist!');
	  return;
	}
	
    peerConnection.createAnswer(function (sessionDescription) { // in case of success
      peerConnection.setLocalDescription(sessionDescription);
      console.log("Sending: SDP");
      console.log(sessionDescription);
      sendSDP(sessionDescription);
    }, function () { // in case of error
      console.log("Create Answer failed");
    }, mediaConstraints);
  }

  function setAnswer(evt) {
    if (! peerConnection) {
	  console.error('peerConnection NOT exist!');
	  return;
	}
	peerConnection.setRemoteDescription(new RTCSessionDescription(evt));
  }
  
  // -------- handling user UI event -----
  // start the connection upon user request
  function connect() {
    if (!peerStarted && localStream && socketReady) { // **
	//if (!peerStarted && localStream) { // --
      sendOffer();
      peerStarted = true;
    } else {
      alert("Local stream not running yet - try again.");
    }
  }

  // stop the connection upon user request
  function hangUp() {
    console.log("Hang up.");
    stop();
  }

  function stop() {
    peerConnection.close();
    peerConnection = null;
    peerStarted = false;
  }

  </script>
</body>
</html>

最初の方も言っていたように, 次以降の記事でjQuery Mobileでのビデオチャットアプリについてもう一回ソースコードも乗せる予定ですが, 自分なりにスッキリさせておいたのを載せますが, 今回はコレで^^;

package.jsonの編集

忘れていたんですが, このpackage.jsonというのはRailsでいうGemfileのようなもので, ここには自分の設定や必要なパッケージ等を書き込みます.
そうするとnpm installしたときに必要なパッケージが自動的に入るというものなんですが, もうローカルでは入れましたので今のところは必要性を感じないんですが, herokuにデプロイするときに重要なので記述しましょう.
以下の手順でまず作っていきましょう.

$ npm init

name: (アプリの名前)
version: (0.0.0) 
description: 
entry point: (app.js) 
test command: 
git repository: 
keywords: 
author: (自分の名前やニックネーム)
license: (BSD) MIT

{
  "name": "XXX",
  "version": "0.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": "",
  "author": "YYY",
  "license": "MIT"
}

といった感じになると思うんですが, 少し書き換えます.

{
  "name": "XXX",
  "version": "0.0.0",
  "description": "",
  "author": "YYY",
  "license": "MIT",
  "dependencies": {
    "express": "^3.14.0",
    "logfmt": "^1.1.2",
    "ws": "0.4.x",
    "socket.io": "1.0.6"
  },
  "engines": {
    "node": "0.10.x"
  }
}

多分これでもいけると思います.
herokuにデプロイするときに, heroku側で自動的にdependenciesの中に記述してあるパッケージをインストールしてくれるはず…です!笑
(wsとかlogfmtとかいらないとは思います笑)

Procfileの作成

これはとりあえず, 「herokuが最初に実行するコマンドを指定する」程度に覚えていただければよいでしょう.
以下のように記述します.

web: node signaling.js

.gitignoreの作成

ローカルではnode_modulesが必要なんですが, herokuにはあげる必要がないので, .gitignoreを作ってあげないようにしましょう.

$ vim .gitignore
# 以下を記述
node_modules

さて, これで一通りアプリは作り終えたので次から本題の(笑)herokuにデプロイする手順を示します.

herokuへのデプロイ

前の方で述べましたが, githubとかのソースコード管理を使ったことがある人には非常に簡単です.
まずherokuに登録していない人は登録しましょう.
登録後は恐らく「Create your app」的な感じのものが表示されると思います.
そこで一旦appを作っておいて, 作った後に出てくる「git URL」が必要になります.
最初に表示されますが, それ以降はappの「settings」で見れます.
これらの手順は直感でも行けると思うので説明を省略します.

また, herokuのサイトで「toolbelt」をダウンロード&インストールしておいてください.
これも簡単なので詳細は省きまして, その後の設定の手順はここを参考にしてください.

そしたら以下の手順でデプロイしていきます.
gitはインストールしてある前提です(妥協).

$ git init
$ git remote add origin <自分のgit URL>
$ git add .
$ git commit -m "first commit"
$ git push origin master

待っているとずらーっと書かれますが, 色々と必要なモジュールを作成しているところです.
終了したら,

$ heroku open

とすると自動的にブラウザを開いて自分のappを開いてくれます.
※ここで注意が必要なのですが, パソコンの音をミュートにしておかないとめちゃくちゃハウリングうるさいので音はミュートにしておきましょう! それかイヤホンとか付けましょう!

以上で完了になりますが, うろ覚えな感じで書いてきましたので, 恐らくエラーは起こるでしょう!笑
でもそういったエラーを処理する力は初心者といえども必要になりますので頑張ってみましょう(;´∀`)笑


それでは今日はこの辺で!
Say good-bye!

プログラミング用フォント"Ricty"のインストール

CUIでプログラミングしている方にオススメなフォントに「Ricty」というフォントがあるらしく, インストールしてみました.
しかし, その際にハマったことも含めてインストール方法を紹介していきます.

インストール方法

とりあえず, どのサイトにも掲載されているように, 普通なら以下の4行でインストールされます.

$ brew tap sanemat/font
$ brew install ricty
$ cp -f /usr/local/Cellar/ricty/3.2.2/share/fonts/Ricty*.ttf ~/Library/Fonts/
$ fc-cache -vf

後はターミナルを再起動して, 環境設定でフォント一覧にRictyが入っているか確認して自分好みの設定にするとよいでしょう!

homebrewに関するエラー

自分は恥ずかしながら最近mavericksにアップデートしたのですが, homebrewがなんだか正常に動作しなかったため, Rictyのインストールでこんな感じのエラーがどうしてもでてきました.

Ricty Generator 3.2.3b
 
Author: Yasunori Yusa <lastname at save.sys.t.u-tokyo.ac.jp>
 
This script is to generate ``Ricty'' font from Inconsolata and Migu 1M.
It requires 2-5 minutes to generate Ricty. Owing to SIL Open Font License
Version 1.1 section 5, it is PROHIBITED to distribute the generated font.
 
./ricty_generator.sh: line 521: 35176 Trace/BPT trap: 5       $fontforge_command -script ${tmpdir}/${modified_inconsolata_generator} 2> $redirection_stderr

最後の行のエラーは検索しても有力な回避方法が得られなかったため, 色々試しても出来なくてかなり苦労しました. ようやくエラー回避できたので以下にその方法を示します.

1. $ brew list で今インストールしてあるformulaを表示する
2. 手当たり次第に$ brew reinstall formulaの名前 をしていく
3. 全部できたら上記のインストール方法を再度試す

これでインストールできるはずです.
項目2の部分は恐らく, lib関係のものをreinstallすればRictyはインストールできると思うんですが, mountain lionの時点でインストールしていたパッケージをmavericksでも使えるようにするためには, 一応全てやっておいたほうが良いのかなと思います.

他にもエラーが起きた方は, 以下を参考にしてみてください.
http://qiita.com/ykirishima/items/23545079c553338f8243

また, Rictyのインストールから有効化までの手順は以下の記事で紹介されています.
http://morizyun.github.io/blog/ricty-font-homebrew-mac/

それでは今日はこのへんで.

Say good-bye!

rubyで書籍検索をしてみた(amazon-ecsを利用)。

amazon-ecsとは?

amazon-ecsは単刀直入に言うと, rubyからAmazon Product Advertising APIを使えるようにしたライブラリで, rubyのライブラリである「Nokogiri」を使ってデータを扱いやすくしています. 本のタイトルやISBNなどをキーワードとして検索を行い, 該当する本に付随する情報を取得してくれます.

※Nokogiriに関してはイメージ的に以下の記事がわかりやすいかもしれません.
http://himaratsu.hatenablog.com/entry/2013/04/27/002249

準備

rubyやらgemやらの環境が整っているのは前提としておいて(ググればいっぱい出てくるので…すみません…), 自分の環境はubuntu server 13.10を使ってやっていきます.


まずは一番面倒であろうAmazonアソシエイトの登録です笑
Amazon Product Advertising APIを使うためにはしょうがない!

ということで一番わかりやすい登録方法が以下のサイトに記載されていますので参考にしていきましょう.
http://www.ajaxtower.jp/ecs/

このサイトではAmazon Web Serviceの使い方全般を紹介してくれていますが, ここでやっていくのは「基本」の「サービス利用準備」までで十分かと思います.
ただし, Secret Access Keyはどうやら表示できないようなので(少なくとも自分は見つけられませんでした笑), 発行されたらどこかにメモして人目につかないように管理しておきましょう!


結局のところ使うのは, 「トラッキングID(associate_tag)」と「アクセスキーID(Access_Key_ID)」と「シークレットアクセスキー(Secret_Access_Key)」なので, そちらを控えておきましょう.

作業

登録が終わったら実際に作業に入ります.
最初に, amazon-ecsをインストールします.

$ gem install amazon-ecs

インストールできたら, 実際にrubyで処理を書いていきます.

#! /usr/bin/ruby
# coding: UTF-8

require 'rubygems'
require 'amazon/ecs'

Amazon::Ecs.options = {
        :associate_tag => "手に入れたトラッキングID",
	:AWS_access_key_id => "手に入れたAccess_Key_ID",
	:AWS_secret_key => "手に入れたSecret_Access_Key"
}
# Amazonモジュール内部にあるEcsクラスのitem_searchメソッドを呼び出す. 'ruby'の部分が検索キーワード
res = Amazon::Ecs.item_search('ruby', {:search_index => 'Books', :response_group => 'Medium', :country => 'jp'})
res.items.each do |item|
        # タイトルを取得&表示
        puts item.get_element("Title")
        # 著者を取得&表示
        puts item.get_element("Author")
end


実行してみると,

<Title>パーフェクトRuby (PERFECT SERIES 6)</Title>
<Author>Rubyサポーターズ</Author>
<Title>たのしいRuby 第4</Title>
<Author>高橋 征義</Author>
<Title>Ruby on Rails 4 アプリケーションプログラミング</Title>
<Author>山田 祥寛</Author>
<Title>Ruby徹底攻略 (WEB+DB PRESS plus)</Title>
<Author>角 征典</Author>
<Title>初めてのRuby</Title>
<Author>Yugui</Author>
<Title>プログラミング言語 Ruby</Title>
<Author>まつもと ゆきひろ</Author>
<Title>パーフェクト Ruby on Rails</Title>
<Author>すがわら まさのり</Author>
<Title>Rubyではじめるシステムトレード (現代の錬金術師シリーズ)</Title>
<Author>坂本タクマ</Author>
<Title>作りながら学ぶRuby入門 第2</Title>
<Author>久保秋 真</Author>
<Title>メタプログラミングRuby</Title>
<Author>Paolo Perrotta</Author>


たった10数行程度で本のタイトルと著者がとれちゃいます.
でも取ってこれる商品数は10個なのでピンポイントで商品のデータを取得したかったらキーワードを詳細にしましょう.
ちなみにISBNとかでもキーワードとして利用できます.

是非図書管理とかに利用して頂きたいと思います.

それでは今日はこのへんで!

Say good-bye!