iPhone から Mac のボリュームを変更する Webアプリを作る

我が家では Mac miniiTunes に音源が蓄積されており、MacDAC → アンプ と接続されている。iTunes であれば Remoteアプリで iTunes のボリュームコントロールは可能だが、時々 Mac のボリュームを調整したくなる。
iOS 用の App としては Volume Control for Mac というものがあり、これで同一LAN上にある Mac のボリュームを調整出来るし、その他にもいくつか方法があるが、lifehacker の記事 - iPhoneなどのiOSデバイスをMacの音量調節リモコンとして使う方法 とそのネタ元の記事を読み、iPhone上の Safari から Mac のボリュームをコントロールすることが出来るのではないかと思い立った。
Mac のボリュームは Applescript を使ってコントロールすることが出来る。これを Webのインターフェースを使って実行するにはどうすれば良いか。Applescript は shell から osascript コマンドを使って起動することが出来る。Web から shell を起動したり、その結果を受け取るのにお手軽なのは php ということになった。php は使ったことがなかったが、Mac OSX には予めインストールされている。
 
まずは環境の準備。
Webサーバーの起動は以前はシステム環境設定から行えたのだが、10.9 では行えなくなっている。起動にはコマンドラインから apachectl を使う。

% sudo apachectl start/stop/restart (起動/停止/再起動)

 
Mac の電源ON で自動で Webサーバーを起動するためには

% sudo launchctl load -w /System/Library/LaunchDaemons/org.apache.httpd.plist

 
自動起動の解除には

% sudo launchctl unload /System/Library/LaunchDaemons/org.apache.httpd.plist

 
apacheの設定ファイル /etc/apache2/httpd.conf を変更する。目的は php を有効にする事と DocumentRoot を変更すること。
 
php を有効にするために 118行目のコメントを外す (”#” を取り除く)

116 LoadModule rewrite_module libexec/apache2/mod_rewrite.so
117 #LoadModule perl_module libexec/apache2/mod_perl.so
118 LoadModule php5_module libexec/apache2/libphp5.so
119 LoadModule hfs_apple_module libexec/apache2/mod_hfs_apple.so

 
必要に応じて DocumentRoot を変更する (170行目)

165 #
166 # DocumentRoot: The directory out of which you will serve your
167 # documents. By default, all requests are taken from this directory, but
168 # symbolic links and aliases may be used to point to other locations.
169 #
170 # DocumentRoot "/Library/WebServer/Documents"
171 DocumentRoot "/Users/foo/web"

 
DocumentRoot を変更したらこちらも変更する (197行目)

195 # This should be changed to whatever you set DocumentRoot to.
196 #
197 #
198
199 #

 
コントロールHTML5 で規定された range を使用する。
基本のビューはこんな感じ。

これをCSSでカスタマイズするが、この出来上がりは後述。
画面を表示するときに AppleScript を使って現在のボリューム値を取得し表示。スライドボリュームでボリュームを変更し、マウスまたは指を離したタイミングで、ボリュームを変更する php を起動し、また最初の画面に戻る。
jQuery Mobile の利用を検討したが、縦位置のスライドバーを実現するのが大変そうだし、使わなければ軽く済むので止めた。Ajax を使うともっとスマートに実現できた可能性があるが、これも良くわかっていないので使わず。
 
volume.php(基本の画面)

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- iPhone 用の表示設定 -->
<meta name="viewport" content="initial-scale=1.0,
								maximum-scale=1.0,
								user-scalable=no" >
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>Volume Control</title>
<!-- 「ホーム画面に追加」用のアイコン --> 
<link rel="apple-touch-icon" href="icon.png" />
<!-- range の表示を変更するcss 
        Portrate(縦) と Landscape(横) で表示を変えている -->
<style>
html {
	color: white;
	background-color: gray;
}
p {
	text-align: center;
}
@media screen and (orientation:portrait) {
	input[type=range].vVertical {
	    position: relative; z-index: 0;
    	-webkit-appearance: none;
    	background-color: black;
    	width: 310px;
    	height:10px;
    	border:5px solid silver;
    	border-radius: 3px;
    	margin-top: 250px;
    	-webkit-transform:rotate(-90deg);
    	transform:rotate(-90deg);
	}
	input[type=range].vVertical:before {
    	content: '';
		height: 2px;
		width: 16px;
		background-color: white;
		position: absolute; z-index: 1;
		-webkit-transform:rotate(-90deg);
		transform:rotate(-90deg);
		top: -16px;
		right: 2px;
		box-shadow: 0px -145px 0px white,
    				0px -290px 0px white,
					-40px -0px 0px white,
					-40px -145px 0px white,
					-40px -291px 0px white;
	}
	input[type=range].vVertical:after {
    	content: '';
		height: 2px;
		width: 10px;
		background-color: white;
		position: absolute; z-index: 2;
		-webkit-transform:rotate(-90deg);
		transform:rotate(-90deg);
		top: -13px;
		right: 33px;
		box-shadow: 0px -29px 0px white,
    				0px -58px 0px white,
					0px -87px 0px white,
					0px -145px 0px white,
					0px -174px 0px white,
					0px -203px 0px white,
					0px -232px 0px white,
					-34px -0px 0px white,
					-34px -29px 0px white,
					-34px -58px 0px white,
					-34px -87px 0px white,
					-34px -145px 0px white,
					-34px -174px 0px white,
					-34px -203px 0px white,
					-34px -232px 0px white;
	}
}
@media screen and (orientation:landscape) {
	input[type=range].vVertical {
	    position: relative; z-index: 0;
    	-webkit-appearance: none;
    	background-color: black;
    	width: 310px;
    	height:10px;
    	border:5px solid silver;
    	border-radius: 3px;
    	margin-top: 130px;
    	-webkit-transform:rotate(-180deg);
    	transform:rotate(-180deg);
	}
	input[type=range].vVertical:before {
    	content: '';
		height: 16px;
		width: 2px;
		background-color: white;
		position: absolute; z-index: 1;
		top: -24px;
		right: 10px;
		box-shadow: -145px 0px 0px white,
    				-290px 0px 0px white,
					0px 42px 0px white,
					-145px 42px 0px white,
					-290px 42px 0px white;

	}
	input[type=range].vVertical:after {
    	content: '';
		height: 10px;
		width: 2px;
		background-color: white;
		position: absolute; z-index: 2;
		top: -18px;
		right: 39px;
		box-shadow: -29px 0px 0px white,
   		 			-58px 0px 0px white,
   		 			-87px 0px 0px white,
   		 			-145px 0px 0px white,
   		 			-174px 0px 0px white,
   		 			-203px 0px 0px white,
   		 			-232px 0px 0px white,
   		 			-261px 0px 0px white,
   		 			0px 36px 0px white,
   		 			-29px 36px 0px white,
   		 			-58px 36px 0px white,
   		 			-87px 36px 0px white,
   		 			-145px 36px 0px white,
   		 			-174px 36px 0px white,
   		 			-203px 36px 0px white,
   		 			-232px 36px 0px white,
   		 			-261px 36px 0px white;
	}
}
input[type="range"]::-webkit-slider-thumb {
	-webkit-appearance: none;
   	position: relative; z-index: 3;
    background-color: red;
    width: 20px;
    height: 60px;
    border: 3px solid #666;
    border-radius: 3px;
    box-shadow: -2px 2px 5px black;
}
</style>
</head>
<body>
<!-- AppleScript で現在の Volume を取得し、文字列→数字変換して変数にセット -->
<?php
$vol = (int) shell_exec("osascript -e 
	\"return output volume of (get volume settings)\"");
?>
<p>
<!-- スライダーの本体 onmouseup はPC用、ontouchend は iPhone用 
     変更を終了したら setVol を呼び出す -->
<input type="range" id="range" min="0" max="100" step="1" 
	value="<?php echo $vol ?>" onmouseup="setVol(this.value)" 	
	ontouchend="setVol(this.value)" class="vVertical"/>
</p>
<!-- スライダーのセットした値を引数に、setVolume.php を起動する -->
<script type="text/javascript">
  	function setVol(newVol){
		urltxt = "setVolume.php?newVolume=" + newVol;
		location.replace(urltxt);
	}
</script>
</body>
</html>

 
(ボリュームを変更するphp)
setVolume.php

<!-- ボリューム値を受け取って AppleScript でボリュームを変更する
   ボリュームは取得する時は 0-100 の範囲だが、セットするときは 0-7 の範囲
     このため、0.07 を乗じる必要がある。 -->
<?php
$newvol = ((int) $_GET['newVolume'])*0.07;
exec("osascript -e \"set volume $newvol\"");
?>
<!-- ボリュームのセットが完了したら、スライダーのページを再表示する -->
<script>
  location.replace("volume.php");
</script>

 
iPhone で利用する事を前提としているが、同一LAN上の HTML5対応ブラウザであれば動作するはずだ。自分の環境では、safarihttp://192.168.0.13/vc/volume.php を開く。css によりスライドはこのような表示になった。目盛りを付けたい気もするが...(shadow で出来そうな気がする)

 
ホーム画面に追加をすると、ホーム画面から一発起動。



 
本記事は、ターミナルでファイルの編集が出来る程度の知識がある人向けです。
利用、改変はご自由に。

追記: 目盛りを付け、アドレスバーを非表示に。画面を横にしても同じように表示されるように CSS を変更した。表示は CSS のみで描画している。