1-10 サウンドを扱う

HTML5+JavaScriptでサウンドを扱う方法を解説します。 (1)がサウンド操作の基本、(2)がサウンド操作の高度な方法となります。
2017年現在、Android系、iOS系ともに スマートフォンのブラウザ上でのサウンドの読込と出力は、ユーザーが画面をタップした時にしか行えない という制約があります。 (2)ではこれを回避する方法を含め解説します。


(1)サウンド操作の基本

サウンドファイルを読み込んで出力する基本のプログラムです。
[読込]→[再生]→[一時停止]の文字を順にタップ(クリック)してご確認下さい。
example1a1.html ← 動作の確認
ソースコードは次のようになります。
01<!DOCTYPE html>
02<html lang="ja">
03<head>
04<meta charset="utf-8">
05<title>JavaScriptのテストプログラム</title>
06</head>
07<body style="font-size:4em; text-align:center;">
08JSでサウンドを扱う基本<br>
09<span onclick="loadSnd()">[読込]</span>→
10<span onclick="playSnd()">[再生]</span>→
11<span onclick="stopSnd()">[一時停止]</span>
12
13<script>
14var bgmPlayer;
15
16function loadSnd() {
17 bgmPlayer = new Audio();
18 bgmPlayer.src = "example1a1.m4a";
19}
20
21function playSnd() {
22 bgmPlayer.play();
23}
24
25function stopSnd() {
26 bgmPlayer.pause();
27}
28</script>
29</body>
30</html>

文字(span要素)をクリックやタップした時に働く関数をonclick属性で指定しています。
サウンドは以下の流れで出力します。

①サウンドを読み込む変数名(オブジェクト名)を宣言
 ↓
②new Audio()命令で Audioオブジェクトを作成
 ↓
③audio.src で サウンドファイルを指定
 ↓
④audio.play()で出力、audio.pause()で一時停止

①~③は

var 変数名 = new Audio( "サウンドファイル" );

と1行で記述することもできます。 但し、このサンプルのように出力、停止などの処理を別々の関数にするには、サウンド用の変数をグローバル変数とします。 グローバル変数とローカル変数は 1-6 (3) で解説しています。

body要素に記述したフォントサイズの指定 font-size:4em; の em は 親要素に設定されたフォントサイズ(この場合はブラウザ標準のフォントサイズ)の何倍にするかという記述になります。


(2)サウンド操作の高度な方法

サウンドファイルには複数のフォーマット(ファイル形式)があります。 (1)で使っている曲はAAC(拡張子m4a)と呼ばれる形式で、多くの環境や機器で出力できます。 他に広く普及しているフォーマットとしてwavやmp3があります。
古いブラウザでAACがサポートされていない場合、ogg形式であれば出力できます。 ここでは再生可能なサウンドファイルの選択や、冒頭で述べたスマートフォンの制約を回避する方法を解説します。
example1a2.html ← 動作の確認
ソースコードは次のようになります。
01<!DOCTYPE html>
02<html lang="ja">
03<head>
04<meta charset="utf-8">
05<title>JavaScriptのテストプログラム</title>
06</head>
07<body style="text-align:center;">
08<canvas style="background-color:#CEF;" id="bg" width="800" height="400"></canvas>
09<div id="proc" style="font-size:2em;"></div>
10<script>
11var canvas = document.getElementById("bg");
12var cnt = canvas.getContext("2d");
13cnt.font = "36px monospace";
14cnt.textAlign = "center";
15cnt.textBaseline = "middle";
16
17//マウスとタップの判定
18var tapX = 0;
19var tapY = 0;
20var tapC = 0;
21
22if( 'ontouchend' in document ) {
23 canvas.addEventListener( "touchstart", touchStart );
24 canvas.addEventListener( "touchend", touchEnd );
25 function touchStart(event) {
26  event.preventDefault();
27  var rect = event.target.getBoundingClientRect();
28  tapX = Math.floor( event.touches[0].clientX-rect.left );
29  tapY = Math.floor( event.touches[0].clientY-rect.top );
30  tapC = 1;
31 }
32 function touchEnd(event) {
33  event.preventDefault();
34  bgmProc();//★ポイント
35 }
36}
37else {
38 canvas.addEventListener( "mousedown", mouseDown );
39 canvas.addEventListener( "mouseup", mouseUp );
40 function mouseDown(event) {
41  var rect = event.target.getBoundingClientRect();
42  tapX = event.clientX-rect.left;
43  tapY = event.clientY-rect.top;
44  tapC = 1;
45 }
46 function mouseUp(event) {
47  bgmProc();//★ポイント
48 }
49}
50
51//キャンバスにボタンを表示する関数 押されたかの判定も行っている
52function drawBtn( str, x, y, w, h ) {
53 cnt.fillStyle = "#FFF"; cnt.fillRect( x, y, w, h );
54 cnt.fillStyle = "#000"; cnt.fillText( str, x+w/2, y+h/2 );
55 if( x <= tapX && tapX < x+w && y <= tapY && tapY < y+h ) {
56  cnt.strokeStyle = "#04F";
57  cnt.strokeRect( x+2, y+2, w-4, h-4 );
58  return tapC;
59 }
60 return 0;
61}
62
63//乱数を返す関数
64function rnd( max ) {
65 return Math.floor( Math.random()*max );
66}
67
68//サウンドの処理
69var AUDIOEXT = ".m4a";
70var audio = new Audio();
71if( audio.canPlayType("audio/ogg") == "probably" ) AUDIOEXT = ".ogg";
72alert( "再生可能なファイルタイプ "+AUDIOEXT );
73
74var snd = 0;
75var bgmPlayer = null;
76
77function loadSnd() {//読み込み
78 bgmPlayer = new Audio( "example1a1" + AUDIOEXT );
79 bgmPlayer.load();
80}
81
82function playSnd() {//再生
83 bgmPlayer.loop = true;
84 bgmPlayer.play();
85}
86
87function pauseSnd() {//一時停止
88 bgmPlayer.pause();
89}
90
91function stopSnd() {//停止
92 bgmPlayer.pause();
93 bgmPlayer.currentTime = 0;
94}
95
96function rateSnd( rate ) {//曲の速度
97 bgmPlayer.playbackRate = rate;
98}
99
100function bgmProc() {
101 switch( snd ) {
102  case 0: break;//何もしない
103  case 1: loadSnd(); break;
104  case 2: playSnd(); break;
105  case 3: pauseSnd();break;
106  case 4: stopSnd(); break;
107 }
108 snd = 0;
109}
110
111//ブラウザを隠した時に音を一時停止する処理
112document.addEventListener( "visibilitychange", vcProc );
113function vcProc() {
114 if( document.hidden == true ) pauseSnd();
115}
116
117window.onload = drawSndCtrl();
118function drawSndCtrl() {
119 if( bgmPlayer == null ) {
120  if( drawBtn( "読み込み", 20, 150, 240, 80 ) == 1 ) snd = 1;
121 }
122 else {
123  if( drawBtn( "再生", 20, 150, 240, 80 ) == 1 ) snd = 2;
124  if( drawBtn( "一時停止", 280, 150, 240, 80 ) == 1 ) snd = 3;
125  if( drawBtn( "停止", 540, 150, 240, 80 ) == 1 ) snd = 4;
126  if( drawBtn( "早回し", 20, 300, 240, 80 ) == 1 ) rateSnd(2.0);
127  if( drawBtn( "遅回し", 280, 300, 240, 80 ) == 1 ) rateSnd(0.5);
128  if( drawBtn( "通常速度", 540, 300, 240, 80 ) == 1 ) rateSnd(1.0);
129 }
130 tapC = 0;
131
132 //曲の進行状況
133 cnt.fillStyle = "#000";
134 cnt.fillRect( 20, 20, 760, 60 );
135 if( bgmPlayer != null ) {
136  var max = bgmPlayer.duration;
137  var now = bgmPlayer.currentTime;
138  cnt.fillStyle = "#0F0";
139  cnt.fillRect( 20+755*now/max, 20, 4, 60 );
140 }
141
142 document.getElementById("proc").innerText = "tapX="+tapX+" tapY="+tapY+" snd="+snd;
143
144 setTimeout( drawSndCtrl, 100 );
145}
146</script>
147</body>
148</html>

今回のソースコードには、次のように、これまで解説した内容の多くが含まれています。

キャンバスを用いている

setTimeout命令によるリアルタイム処理で、ボタンが押されたかの判定、曲の進行状況を表示している
(ボタンを表示しそこが押されたか判定する関数を実装)
(曲進行は、サウンドファイルの長さと、現在の再生位置の値を元に表示)

タップとマウス入力を実装
 if( 'ontouchend' in document ) でタップ入力できるか判定しています。 タップ入力できる場合 'ontouchend' in document は true となり、touchイベントが働きます。 false であれば mouseイベントが働きます。

サウンド制御の関数を実装
サウンド制御のポイントは、再生、停止などの各ボタンが押されたらフラグ(変数snd)に値を入れ (フラグの値はファイルの読み込みが1、出力が2、一時停止が3、停止を4としている)、 実際のサウンド処理はタップの指を離した時もしくはマウスボタンを放した時に行っている点です。 touchEnd と mouseUp に記述した bgmProc()で、フラグに応じたサウンド処理を行っています。 これが先述したスマートフォンの制約を回避する方法です。

再生できるサウンドのフォーマットを調べる
69~71行でサウンドのフォーマット(ファイルの拡張子)を決めています。 audio.canPlayType("audio/ogg") == "probably" となればogg形式のファイルが再生できます。 サウンドは m4a と ogg の二種類を用意してHTMLと同一階層に置いています。

もう一つ重要な処理を実装しています。 JavaScriptで曲を再生するとブラウザがバックに回っても音が流れ続けるので、それを防ぐソースコードです。 その部分を抜粋します。

document.addEventListener( "visibilitychange", vcProc );
function vcProc() {
 if( document.visibilityState == "hidden" ) pauseSnd();
}

ブラウザが隠れたかを判定し、隠れた場合は音を一時停止しています。 記述の基本はマウスやタップを判定する addEventListener と一緒で、 マウスやタップは canvas に addEventListener しましたが、 ブラウザが隠れたかの判定では document に対し addEventListener します。 document.visibilityState == "hidden" がブラウザが隠れた時となります。

以下にサウンド関連の命令をまとめます。
new Audio()オーディオオブジェクトを生成
audio.srcサウンドファイルを指定
audio.load()明示的に読み込みを行う
audio.play()サウンドを出力
audio.pause()サウンドを一時停止
audio.loop繰り返し出力するか
繰り返すならtrueを指定
audio.playbackRate再生速度
audio.currentTime現在の再生位置(値は秒数)
audio.durationサウンドファイル全体の長さ(秒数)
playbackRate は1.0が通常速度で、値が大きいと速く、小さいと遅くなります。
※古いブラウザやスマートフォンではloopとplaybackRateは機能しないことがあります。

ボタンを表示する自作関数
今回のソースコードでは drawBtn( str, x, y, w, h ) という関数を用意し、 「再生」「停止」などのボタンを表示し、そこが押されたか判定しています。 この関数は押された座標 tapX, tapY がボタンの枠内にあれば tapCの値を返します。 tapCはイベントリスナーでクリックもしくはタップされた時に1にしていますので、 この値でボタンが押されたか判る仕組みになっています。
ソフトウェア開発ではこのように処理や判定で便利に使える関数を用意することがポイントとなります。


m4aとoggのブラウザ対応状況
私の手持ちの機器と環境で2種類のフォーマットが流れるか確認しました。結果は

m4a、ogg共に再生可能なもの(内はバージョン)
・パソコン(Windows7) ブラウザChrome(57) 及び Firefox(52)
・Android(4.0以上)端末全般、初代Kindleも両方再生できました

m4aのみ再生可能
・パソコン(Windows7) IE(11)
・パソコン(Windows10) Edge(38)
・iOS端末全般

初期のAndroid(2.2)端末でm4a、oggどちらも再生できないものがありました。

この結果を見ると、近い将来、AAC形式で全ての環境に対応できると思われます。


◇コラム◇ HTML5のサウンドに物申す
HTML5とJavaScriptでのサウンド制御は、CやJavaで開発するネイティブアプリと比べると弱い部分があります。 ネイティブアプリでは複数の音を同時出力できますが、HTML5では環境によっては1音しか再生できません。 またサウンド関連の命令の一部が機能しないこともあります。 ブラウザの新しいバージョンや新しいスマートフォンでは、サウンド面も強化されつつあり、今後に期待したいと思います。
発展途上にあるJavaScriptのサウンド制御ですが、みなさん、ぜひ、著作権フリーのサウンドなどを利用して、ここで解説した方法で音を鳴らしてみて下さい。 ゲームソフトでサウンドは重要な要素であり、音の有無や雰囲気はゲームの面白さに影響します。



前のページへ / 次のページへ

お気軽にお問い合わせ下さい →