読者です 読者をやめる 読者になる 読者になる

createObjectURLがすごい件

JavaScript

最近またLocal Player (Chrome Player)を実装しなおしています.

一応説明しておくと, Local Playerは, 完全にローカルで動作する音楽プレイヤーです.

シンプルさを求め(実装がめんどくさいだけ), 操作しやすく(これは大事), 良い感じのプレイヤーです.

半年前に, ソースコードがスパゲッティになって, 開発を中断していましたが, 最近また書きなおし始めたのです.



音楽プレイヤーをブラウザー上で実装するのには, 音楽ファイルをJavaScriptで読み込まなければなりません.

そこでHTML5ですよ!!!

<audio src="url/to/musicfile.mp3" type="audio/mp3" />

みたいな感じで, 音楽を再生できます.

詳細は他のページに譲ります.



さて, 音楽ファイルを再生するには, ローカルファイルからurlをaudio要素のsrcにファイルをぶち込まなければならないわけですが, どうすればいいでしょう.

まぁ素直な実装は, File APIFileReaderreadAsDataURLを使うことでしょう.

実際, Local Playerは今までこの方法をとっていました.

だいたいこんな感じの実装です

// file は<input>から入ってきたやつ
var reader = new FileReader();
reader.onerror = function(e) {
  console.dir(e);
};
reader.onload = function(e) {
  var audio = new Audio(e.target.result);
  audio.volume = 0.5;
  audio.addEventListener('ended', function() {
    delete audio;
    next_music();
  });
  audio.play();
};
reader.readAsDataURL(file);

極めて普通ですね.

教科書そのまんまって感じです.

deleteはいらないと思いますが, e.target.resultが馬鹿でかい文字列となっており, メモリーがやばいことになります.

この実装を用いたLocal Playerで, 音楽を再生して, 10秒再生したらすぐ次のファイルへとスキップする, という動作を繰り返した時, CPU使用率とメモリー使用率は次のようになります.

確認環境はUbuntu, メモリーは2GBです.

曲を開始した瞬間, 5MBの音楽ファイルをreadAsDataURLして, メモリーが一時的に上がります.

大体, 山の大きさがメモリーの5%くらい, つまり, およそ100MBの文字列がJavaScriptの中でできては消え... を繰り返してるのです.

こんなんだと, 1秒ごとにスキップして行ったらどんどんメモリーは食うはCPUはしんどいわ...

しかも, 「曲をスキップする」というのは音楽プレイヤーとしては, よくあることなのです.

で, 一年間ほど悩んでいました.



今日, 神様のお告げが

ξ*‘ ー‘)<( それcreateObjectURLでデキルヨ )


え...???

どう違うの???

取り敢えず実装してみよう...

var createObjectURL 
  = window.URL && window.URL.createObjectURL
    ? function(file) { return window.URL.createObjectURL(file); }
    : window.webkitURL && window.webkitURL.createObjectURL
      ? function(file) { return window.webkitURL.createObjectURL(file); }
      : undefined;
if (!createObjectURL) return;
var audio = new Audio(createObjectURL(file));
audio.volume = 0.5;
audio.addEventListener('ended', function() {
  delete audio;
  next_music();
});
audio.play();

だいたいこんな感じの実装

はい, とってもとーっても簡単ですね (にこにこ

取り敢えずさっきと同じ条件で, 音楽を読み込んで次々とスキップしてみます


.......

いやいやいやいや...

メモリー...一定じゃん...

これ, ブログ見てる人には分からないでしょうが, ちゃんと音楽も聞こえてますし, スキップしてますし...



分かりやすいように, 音楽を再生する瞬間にCPUにワザと負荷を与えるような感じのコードを入れてみます.

for (var i = 0; i < 2e4; i++) { 
  console.log(i);
};

これで, さっきと同じ条件で行ってみます.

ちゃんと音楽流れてるんですよ? ホントに. ホントです.

メモリー全然食ってないじゃん...\ヤベェ/

まとめ

createObjectURLは凄い*1

何が凄いって, 全然ふっつーの関数呼び出しで書いてるのに, 裏ではurl先のローカルファイルをstream(?)的に読み込んでる.

あ, いや, 読み込んでるのはaudio elementの方なのかな...

これまでは, readAsDataURLで一気に読み込んでいたため, 50MBある1時間超のファイルを再生できませんでした.

それがcreateObjectURLによって解決されてしまったのです.

さらに, これまでできなかった動画再生も可能になります!!! ← なりました!!!

createObjectURL, どういう実装になってるんでしょう?

ちゃんと音楽や動画のシークもできるし, すごく不思議です.

不思議なのは, USB接続の外付けHDDなどにある音楽を, 再生して直ぐにUSBをぶっこぬいても, そのまま音楽が聞こえ続けることなんです.



フッ, またV8の闇に取り込まれそうになってしまったぜ...

追記(2012/11/21)

Local Playerはmanifest_version: 2の波についていけなくてStoreから削除されました.

*1:どうしよう, 凄いって思った次の日には, このURLが一時的なものだということに気がついて萎えた. みんなココで困ってる. 例えばHow to save the window.URL.createObjectURL() result for future use? createObjectURLに関するセキュリティとか勉強したい