PHPで音源データを出力してみたかった

今回は、趣味がプログラミングな変態がメディアデータをPHPで出力させてみたかった為に
およそ1カ月とちょっとほど悩んだPHPで音源データを出力する方法について書いてみようかと思う

まず、なぜそのようなことをしようかと思ったかというと
「メディアデータってWeb上でダウンロードできるように作ってなくても割と簡単にダウンロードできちゃうよなぁ~
ダウンロードできないように出来ないものかな」という発想から、
「メディアデータと言えど所詮はデータ、データならプログラムが読んで出力することができるのではないだろうか
それができれば htaccessの設定をすり抜けて目的のメディアデータを取得することができるのではないか
取得した時にどこからアクセスしたかPHPが確認して、思い通りの場所からのアクセスなら目的のメディアデータを出力すれば
ダウンロードできないメディアデータを作ることができるのでは?」
と思い、その発想をそのまま記事にしたのが、PHPとhtaccessを使って画像などのダウンロードをさせない方法
という記事だった
ただ、音源データはそんなにすんなりいかなかった

ここからがつまずきポイント
うまくいかない原因はプログラムがメディアデータを出力していることであり、
プログラムがメディアデータを出力するということは
必然的に受け取った側はそのデータがメディアデータという認識はないのである(なぞのバイナリデータが来たぞ位の感覚なんだろう)
それもそのはず受け取った側は “レスポンスヘッダー” なる場所の “Content-type” を見てバイナリデータを処理しているからなのだから
運よく、PHPには簡単にレスポンスヘッダーを作って渡す関数【Header関数】があった
ただ、その時点で思った、
これはもしかすると 単純にメディアデータにアクセスしたときのレスポンスヘッダーの挙動を完全にまねて
「Header関数として出力しなければならないのではないか」

レスポンスヘッダーなんて ちゃんと理解していなかった私にとってこの気付きは
「メディアデータと同じにすればいいんでしょ」位にしか思ってなかった(ラスボスであるStatusCodeの存在なんて200と404、500等があるんでしょ位にしか考えてなかった)
PCでの動作はこれでよかったのだが、問題はiPhone,iPad こいつらだなぜか動作がレスポンスヘッダーを完全に無視しているようにしか思えない動作をしている
(そのまえにシークができない問題で半月潰している Accept-Renges: bytes に気付いたのは何がきっかけだったか嬉しすぎて忘れた)
ちょっと調べてみるとラスボスの顔を拝むことができた
【HTTP RANGEに対応しないとiPhoneでは再生されません】これは聞きなれないのが出て来たぞ HTTP RANGEってなんだ
調べるうちに芋ずる式に
【StatusCode 206 はCONTENT RANGE を持たないと出力することができません。】
【CONTENT RANGEの値が正しくない場合、その後の処理を行うことができません】
【iPhone・iPadではRangeを無視したContent Rangeが発行されたときメディアを出力することができません】
もはや何が何だかだったが、何やら iPhone・iPadではリクエストヘッダに 【Range】 を持ってて、
このRangeで指定した範囲のバイナリを出力して、ヘッダーにどこのバイナリなのかを知らせる【Content Range】
を返さないと再生できないらしいということが分かったが、PCのブラウザではリクエストヘッダーに【Range】がないことがある
PHPでこれらの挙動をすべて処理しきらなければならないのだが、Rangeを対応したコードを公開しているところはあれど、
その両方に対応しているコードを公開しているところはなかったので、忘却録的にここにメディアデータとほぼ同じ挙動をする
PHPプログラムを掲載する
参考ページhttp://www.phppro.jp/qa/2654


function mediaOut($path){
	define("PATH"$path);
	define("SIZE",filesize(PATH));
	header("Accept-Ranges: bytes");
	$mime_type = mime_content_type(PATH);
	header("Content-type: {$mime_type}");
	if (isset($_SERVER["HTTP_RANGE"])){
		list($rangeOffset, $rangeLimit) = sscanf($_SERVER['HTTP_RANGE'], "bytes=%d-%d");
		if($rangeLimit == ""){
			$rangeLimit = filesize(path)-1;
		}
		$contentRange   = sprintf("bytes %d-%d/%d", $rangeOffset, $rangeLimit, SIZE);
		header("Content-Range: " .$contentRange);
		header("Content-Length: " .($rangeLimit - $rangeOffset + 1));
		header("ETag: \"" .md5( $_SERVER["REQUEST_URI"] ) . SIZE."\"" );
		if($rangeOffset != 0){
			http_response_code (206);
			$contentPointer = fopen(PATH, "rb");
			fseek($contentPointer, $rangeOffset);

			$load = 8192;
			$loop = ceil($rangeLimit / $load);
			$counter = 0;
			$bf="";
			@ob_end_clean();
			while($counter < $loop&& !connection_aborted()){
				set_time_limit(0);
				$bf = fread($contentPointer, $load);
				echo $bf;
				$counter++;
				@flush();
				@ob_flush();
			}
			fclose($contentPointer);
			
		} else {
			http_response_code (200);
			readfile(PATH);
		}
	} else {
		http_response_code (200);
		header('Content-Length: ' . filesize(PATH) );
		header("Content-Range: bytes 0-".(filesize(PATH)-1)."/".filesize(PATH) );
		readfile(PATH);
	}
}