脱FlashのためのJavaScript – オーディオファイルのプリロード考える
Flashって便利ですよね。10年以上前に流行して、爆発的に使用するユーザーも増えて面白いFlashとかカッコイイFlashとか色々見ることができました。
ネットで流行ったFlash動画を見てFlashを始めた方も多いのではないでしょうか?私もその中の一人でした。
しかし現在ではwebを閲覧する端末も、モバイル端末へシェアが移行しつつあり、PCでwebを閲覧するユーザーも減少してきています。それに伴い、Flashを再生する機会も年々減ってきています。
Flashは、グラフィックや音声を一つのパッケージとして書き出し、HTMLに張り付けることで手軽にインタラクティブな表現することが可能なメディアでしたが、上記理由からHTML上のFlashファイルは既にページ閲覧者に対して伝わらない媒体となってしまった気がします。
それではFlashを禁止した場合、どのような代替えがあるかを考えます。
FLASHでできたことをJavaScriptで表現してみよう
現在では、再生開始から終了まで変化のない一直線なアニメーションなどは、mp4などの動画ファイルとして作成してページに張り付けても問題はないと思います。
しかし、ボタンを押したタイミングなどで何らかのアクションを起こさせるような仕掛けはスクリプトを用意しなければなりません。
また、ユーザーのことを考えるとプラグインなども極力インストールさせずに、ネィティブな状態で実現させることが望ましいです。WindowsXP未満の端末であれば、標準でJAVAがインストールされていたのでJAVAを使用したプログラミングという選択肢もあったかと思います。
他にも色々選択肢はあるかもしれませんし安直な考えになるかもしれませんが、Flashで出来ていたことをとりあえずJavaScriptで表現できないかを考えてみます。(JavaScriptなら殆どのブラウザで対応していますものね)
手始めに今回は(次回があるか不明だけど)、オーディオファイルのプリロードをJavaScriptで行う場合の方法を考えてみました。
Flashと言えばプリロード
Flashを見たことある方にはお馴染みかと思いますが、例の “Now loading …” ですね。
これは、Flash内で使用するファイルが十分な容量までダウンロード出来てから再生を行うための待機画面です。
例えば、ページ内にボタンを設置して、ボタンを押すことで予め用意したオーディオファイルを再生させる。というような仕掛けを作りたい場合は、再生するオーディオファイルが完全にダウンロードされるまで待機させなければなりません。必要なオーディオファイルが複数ある場合は、待機時間はさらに長くなります。
ダウンロードがすぐ完了するようなファイル容量であれば、待機時間は考えなくても良いかもしれません。しかし、時には大きいファイルデータを扱うこともあると思います。そうした場合には、やはりプリロード画面が必要になります。
HTML上でオーディオファイルを使用する場合の一般的な方法
HTML上でオーディオファイルを使用する場合は、以下の様に audio タグを使用して貼り付けるのが一般的でしょうか。audioタグにpreload属性で”auto”とすれば、ページ表示のタイミングで、オーディオファイルのプリロードが開始されます。
<audio src="music.mp3" preload="auto" controls></audio>
貼り付け例
audoタグでmp3ファイルを貼り付け
audoタグでoggファイルを貼り付け
audioタグを使用することで、ページにファイルが張り付けられ、再生ボタンを押すことでファイルを再生することが可能になりました。
Flashのように(?)自動的に任意のタイミングでオーディオファイルを複数再生させるにはどうするか?
Flashで良くある(?)メインBGMをバックに、数種類の効果音を任意のタイミングで自動的に何度も再生を行う・・・というシチュエーションだと、audioタグでファイルを貼り付けて云々という方法はとれません。プログラム的にオーディオファイルを制御する必要がありますし、オーディオファイルが読み込まれる前にファイルを再生させる命令来てしまったら問題が発生します。
このような問題を解決するためには、使用する複数のオーディオファイルを全て読み込ませるまでプログラム実行を待機させてやる必要があります。
今回は、複数のオーディオファイル読み込みが完了するまでプリロードアイコンを表示させてページを隠し、読み込み完了したらページを表示して操作可能な状態にするスクリプトを組んでみます。
複数のオーディオファイルをプリロードさせるJavaScriptのサンプル
まずは複数のオーディオファイルを複数読み込むまで待機するスクリプトのサンプルですが、以下の様な物を作ってみました。
スクリプトを組む上で、
- 読み込むオーディオファイルが何個になっても対応可能なスクリプトを考える
- 読み込むオーディオファイルは、全てAudioオブジェクトに登録し、後にJavaScriptで制御可能な状態にすること
- 読み込みを行うオーディオファイルが全て使用できるようになるまでしっかり待機させる
という要点を踏まえて作成していきます。
#preload { background-color: #000; width: 100%; height: 100%; position: fixed; z-index: 999; top: 0px; bottom: 0px; left: 0px; right: 0px; } #loading_icon { width: 100%; height: 120px; background-image: url(image/wheel.svg); background-position: center center; background-repeat: no-repeat; background-size: 120px 120px; margin-bottom: 15px; } #loading_percentage { color: #fff; font-size: 100%; font-weight: bold; margin-bottom: 5px; } #loading_bar { height: 3px; background-color: #FFB4A3; margin-left: auto; margin-right: auto; } .button { cursor: pointer; background-color: #444; border: 2px solid #eee; border-radius: 3px; color: #eee; text-align: center; padding-top: 5px; padding-bottom: 5px; padding-left: 12px; padding-right: 12px; width: 200px; }
<div id="button_output">Play button output</div> <p id="output"><strong>Audio setup log ...</strong></p> <p id="output_event"><strong>Event log ...</strong></p> <div id="preload">Loading...</div>
$(window).load(function(){ $("#button_output").empty();// ボタンを出力する要素内を空っぽにする var sound_array = ["./sound/music.mp3","./sound/se1.mp3","./sound/se2.mp3"];// 読み込みファイル mp3 var sound_array_sub = ["./sound/music.ogg","./sound/se1.ogg","./sound/se2.ogg"];// 読み込みファイル ogg var sound_num = sound_array.length;// ファイル数 var read_count = 0;// プリロードのファイルカウント用 var preload_completion = 0;// プリロード完了のフラグ | 未完 = 0 , 完了済み = 1 // 全てのAudioを停止するボタンの生成 $("#button_output").append( '<p id="stop" class="button">stop</p>' ); // プリロードオブジェクトの生成 $("#preload").html( '<div id="loading_icon"></div><div id="loading_percentage"><span>0</span> / 100 %</div><div id="loading_bar"></div>' ); var loading_bar_width = $("#loading_bar").width();// プリロードバー要素の長さを取得しておく var loading_icon_height = $("#loading_icon").outerHeight();// プリロードアイコン要素の高さ var loading_percentage_height = $("#loading_percentage").outerHeight();// プリロード値要素の高さ var loading_bar_height = $("#loading_bar").outerHeight();// プリロード値要素の高さ var body_height = window.innerHeight;// ブラウザの高さ var loading_margin = ( ( body_height - ( loading_icon_height + loading_percentage_height + loading_bar_height ) ) * 0.45 );// プリロード表記が真ん中に来るように計算 $("#loading_icon").css({ "margin-top": loading_margin + "px" });// プリロードアイコンの上部マージン $("#loading_bar").css({ "width": 0 + "px" });// プリロードバー要素の幅を0にしておく // n番目のAudioファイルをJSで定義する function setup_audio(n) { // Audio定義名を生成 "sound_0" , "sound_1" , "sound_2" ... var sound_name = "sound_" + n; $("#output").append( "<br />sound_name = " + sound_name + " ... " ); // Audio 定義 | canPlayType で分岐 "probably" 確実に対応 , "maybe" 対応/非対応が曖昧 , "" 非対応 sound_name = new Audio(sound_array[n]);// Audioを定義 (Aは大文字) var file_path; file_path = "sound_url = " + sound_array[n];// mp3 if ( sound_name.canPlayType("audio/mp3") === "" ) { sound_name = new Audio(sound_array_sub[n]); file_path = "sound_url = " + sound_array_sub[n];// ogg } $("#output").append(file_path); sound_name.load(); sound_name.volume = 0.35;// 状況に合わせて音量を調整 // Audioの各イベントハンドラ生成 sound_name.addEventListener("loadstart", function(){ $("#output_event").append( "<br />sound_" + n + " 読み込み Start ..." ); }, false); sound_name.addEventListener("canplaythrough", function(){ $("#output_event").append( "<br />sound_" + n + " ファイル再生しても大丈夫!" ); // Audioのプリロードはここで判断 | このイベントが動作する度にカウントして、ファイル数分カウントされたらプリロード完了とする。 read_count++;// 読み込み完了カウントup // プリロードの割合を計算 preload_rate = Math.ceil( ( read_count / sound_num ) * 100 ); $("#loading_percentage span").text( Math.ceil(preload_rate) );// パーセンテージ / 100 % $("#loading_bar").css({ "width": preload_rate + "%" });// プリロードバーの幅変更 if ( read_count >= sound_num && preload_completion === 0 ) { preload_completion = 1;// Audioプリロード完了! ここは一回だけ発火するようにしたい $("#output_event").append( "<br />##### Preload completion #####" ); $("#preload").remove();// プリロード要素削除 } }, false); sound_name.addEventListener("play", function(){ $("#output_event").append( "<br />sound_" + n + " 再生開始♪" ); }, false); sound_name.addEventListener("ended", function(){ $("#output_event").append( "<br />sound_" + n + " 再生終了♪" ); }, false); // 再生ボタンを生成 $("#button_output").append( '<p id="play' + n + '" class="button">play' + n + '</p>' ); // n番目をクリックしたときに、対応したAudioを再生 $( "#play" + n ).on('click', function() { sound_name.currentTime = 0; sound_name.play(); }); // stopボタンを押したときにAudioを全て停止 $("#stop").on('click', function() { sound_name.pause(); sound_name.currentTime = 0; }); } // 各サウンドのイベントを定義 | audio個数分のサウンド定義を行う for ( var n = 0; n < sound_num; n++ ) { setup_audio(n);// forでファイルの個数分処理する } });
サンプルページ Preloading audio | JavaScript
https://weblabyrinth.net/production-files/js-sound/
以下スクリプトの説明
var sound_array = ["./sound/music.mp3","./sound/se1.mp3","./sound/se2.mp3"];// 読み込みファイル mp3 var sound_array_sub = ["./sound/music.ogg","./sound/se1.ogg","./sound/se2.ogg"];// 読み込みファイル ogg
まず読み込みを行うオーディオファイルのパスを配列にいれます。ファイル数は何個でもかまいませんが、今回は3個にしてみました。
また、ブラウザによってはmp3ファイルがサポートされていないものもあるので、ogg形式のファイルも用意しています。
for ( var n = 0; n < sound_num; n++ ) { setup_audio(n);// forでファイルの個数分処理する }
途中関数を挟んで下のほうで、配列に入れたオーディオファイルの個数分 for で関数を実行しています。
nに0から順に数値を代入し、関数 setup_audio() 内で、Audioオブジェクトに登録、イベントハンドラ定義、音を制御するボタンの生成を行っています。
始め、forの中で全ての処理を行おうと思ったのですが、forの内部でAudioオブジェクトの登録やイベントハンドラ定義を行うと、うまく動作しなかったためforの外部に関数を作り、forの内部では0からの値を代入しながら関数を実行するだけにしました。
次は、関数 function setup_audio(n) { の中身に入ります。
var sound_name = "sound_" + n;
まず引数に代入された値を0から順に、Audioオブジェクトに登録する名前を生成していきます。
0から始まって sound_0, sound_1 , sound_2 … という名前が作られます。
$("#output").append( "<br />sound_name = " + sound_name + " ... " ); sound_name = new Audio(sound_array[n]);// Audioを定義 (Aは大文字) var file_path; file_path = "sound_url = " + sound_array[n];// mp3 if ( sound_name.canPlayType("audio/mp3") === "" ) { sound_name = new Audio(sound_array_sub[n]); file_path = "sound_url = " + sound_array_sub[n];// ogg } $("#output").append(file_path); sound_name.load();
先ほど生成された名前 sound_0, sound_1 , sound_2 … に、配列に収納された各オーディオファイルへのパスが紐付けされます。
ifで使用している canPlayType関数では、ファイルタイプからこのブラウザで再生できるか否かのチェックを行っていて、再生可能であれば probably 対応状況があいまいな場合は maybe 非対応の場合は何も文字列を返しません。
このチェックで何も文字列を返さない場合は非対応と判断して、ifの中でファイルパスをoggファイルに変更を行っています。
これでページが読み込まれると同時に.load()関数で各オーディオファイルの読み込みが始まるようになります。
次は、Audioオブジェクトのイベント処理に移ります。
sound_name.addEventListener("canplaythrough", function(){ $("#output_event").append( "<br />sound_" + n + " ファイル再生しても大丈夫!" ); // Audioのプリロードはここで判断 | このイベントが動作する度にカウントして、ファイル数分カウントされたらプリロード完了とする。 read_count++;// 読み込み完了カウントup // プリロードの割合計算を計算 preload_rate = Math.ceil( ( read_count / sound_num ) * 100 ); $("#loading_percentage span").text( Math.ceil(preload_rate) );// パーセンテージ / 100 % $("#loading_bar").css({ "width": preload_rate + "%" });// プリロードバーの幅変更 if ( read_count >= sound_num && preload_completion === 0 ) { preload_completion = 1;// Audioプリロード完了! ここは一回だけ発火するようにしたい $("#output_event").append( "<br />##### Preload completion #####" ); $("#preload").remove();// プリロード要素削除 } }, false);
サンプルのスクリプトには他にもイベントハンドラが登録されていますが、各Audioオブジェクトに変化があったタイミングが把握できるようにメッセージを表示させているだけなので省略します。各イベントハンドラの詳細は、以下のサイトを参考にしてみてください。
HTMLクイックリファレンス | video要素、audio要素をJavaScriptから操作する-HTML5のAPI、および、関連仕様
http://www.htmq.com/video/
重要なのは、上記イベント canplaythrough で、このイベントは、実はオーディオファイルは全て読み込み完了はしていないけど、
今再生すると、最後まで途切れずに再生できる程度までバッファリングされてますよ(十分読み込まれていますよ)という状態になると実行されるイベントです。
最初のほうで、オーディオファイルが完全に読み込み完了したら・・・という話をしていましたが、Video/Audioオブジェクトのイベントで、読み込み100%完了時に発生するイベントが存在しないようなので、canplaythroughイベントで代用しています。
正確に、全てのファイル100%完了した場合・・・を目指したい場合は、PHPなどで各ファイルのバイト数を取得するなどの工夫が必要となりそうです。
つまり、このイベントが読み込むファイルの個数分発生すれば、プリロード完了(実行するに十分なバッファリングまで完了した)ということになりますので、このイベントの中で変数を一つずつカウントアップ(read_count++)して、ファイルの個数と同じ値になったらプリロードアイコンで隠していた要素を非表示させるという仕掛けを作っています。
また、カウントアップのタイミングでその都度、ファイルのダウンロード完了済み個数と必要ファイル総数との割合を計算し、プリロード中のパーセンテージ数とプリロードバーの長さの割り当てを行っています。
とりあえず使用したいオーディオファイルのダウンロード完了まで待機 が完成
とりあえず使用するオーディオファイルの準備が整うまで待機し、準備完了したらページを表示するところまでは完了しました。
今回は関数内で生成したボタンを押すことで、オーディオファイルを鳴らすことは可能ですが、これをまた別の関数内で操作する・・・となると、また少々複雑な仕掛けが必要になってくると思います。
その先は、また色々試してみて良さそうなスクリプトができれば投稿してみたいと思います。
近年のwebでは、Flashの様にいきなり音声が出るサイトは敬遠されがちですが、Flash動画のように音声が出ることを前提に閲覧させるシチュエーションもまったく無いわけではないので、こういった手法を覚えておくことでページ作りの幅が広がるかもしません。何か良いアイデアがあれば流用していきたいですね。
- 2015年8月24日
- jQuery/JavaScript
- 8件のコメント
SHIBAZAKI様
大変ご無沙汰しております。佐藤です。
ご返信を確認するのをこちらの不手際で失念してしまっておりました。
その日のうちにご迅速にお返事くださっていたにもかかわらず、
何ヶ月以上も返信をしないという、なんとも不躾な対応をしてしまい、
大変申し訳ございません。
心よりお詫び申し上げます。
今後はこのことようなことがないよう心がけます。
まだゲームは完成していませんが、
音のローディングの仕組みが大変参考になりました。
許可を下さり誠にありがとうございました。心よりお礼申し上げます。
佐藤様こんにちは。
お気になさらずゲーム作りに励んでいただければ幸いです。
頑張ってください。
SHIBAZAKI様
こんばんは。佐藤です。
温かいお言葉をいただき、感謝しております。
この度は、ご丁寧ご対応いただき、誠にありがとうございました。
SHIBAZAKI 様
ご無沙汰しております。佐藤です。
ゲーム公開いたしました。
大変お世話になりました。
心よりお礼申し上げます。
https://www.freem.ne.jp/win/game/11815
佐藤様こんばんは。
ゲーム拝見させていただきました。
自分のスクリプトがゲームに使われるなんて感慨深いですね。
ゲームの方は絵本調で優しいデザインのキャラクターやオブジェクトが良かったです。
特にレースで表現した雷とか着想が面白かったです。
これからも創作活動頑張ってくださいね。
ご報告ありがとうございました。
SHIBAZAKI様
お世話になっております。
パソコンの調子が悪く、このメッセージがきちんと届くかわからないのですが
ご感想と励ましのお言葉ありがとうございました。
プレイまでしていただいて、とても嬉しく思います。
心よりお礼申し上げます。
gmailの不具合か、
いつもこのコメント欄の返信通知がメールアドレスに届かないので返信不要ですが、
毎度ご丁寧なご対応をしてくださり感謝しております。
この度はありがとうございました。
はじめまして、こんにちは。フリーゲーム開発を個人でしている佐藤と申します。
weblabyrinth.netに載っているソースを利用してもよろしいでしょうか?
ゲームに合わせて加工し、ゲームファイルの一部としてアップする予定です。
アップしたものをプレイヤーがダウンロードして遊ぶ形です。
佐藤様こんにちは。
スクリプトを使用したことによってトラブルなど発生しても当方では責任を負いかねませんが、
当サイトでページ内にコードとして公開しているスクリプトに関しては、自由にお使いいただいて構いません。
フリーゲームが完成しましたら、後で教えていただけると嬉しいです。