- Audio(url)
- Audio(arraybuffer)
- Audio(blob)
- WebAudio(arraybuffer)
- WebAudio(blob)
http://uupaa.net/issues/3/ の結果
操作方法: loadAudio
▶ 画面が緑になるまで待つ ▶ playAudio
の順にクリックする
-
YES -
[loadAudio]
▶ 待つ ▶ 緑になる ▶[playAudio]
で再生が始まる -
空白 -
[loadAudio]
▶ 待つ ▶ 変化がない ▶[playAudio]
で再生も始まらない -
FUZZY -
[loadAudio]
▶ 待つ ▶ 変化がない ▶[playAudio]
で突然再生が始まる
canplay や progress イベントが信用できない -
iOS 6.1 - iPhone 3GS iOS 6.1
-
iOS 7.1 - iPhone 4S iOS 7.1
-
iOS sim - iPhone 7.x simulator
-
iOS 8.0 - iPhone 5 iOS 8.0 GM Seed
-
Mac WebKit 6.0.x - Mac Book Pro + WebKit 6.0.x nightly
-
Mac Chrome 39 - Mac Book Pro + Chrome canary 39 (64 bit)
-
Android Chrome 37 - Nexus 7 (2012) Chrome for Android 37
1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|
iOS 6.1 | YES | FUZZY | YES | ||
iOS 7.1 | YES | YES | YES | ||
iOS sim | YES | YES | YES | ||
iOS 8.0 | YES | YES | YES | ||
Mac WebKit 6.0.x | YES | YES | YES | ||
Mac Chrome 39 | YES | YES | YES | YES | YES |
Android Chrome 37 | YES | FUZZY | YES | YES |
MBP に簡素なWebサーバを設置(npm install http-server)し http://uupaa.net/issues/3/ と同じテストを行った結果
1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|
iOS 6.1 | YES | ||||
iOS 7.1 | YES | YES | |||
iOS sim | YES | YES | |||
iOS 8.0 | YES | YES | YES | YES | |
Mac WebKit 6.0.x | YES | YES | |||
Mac Chrome 39 | YES | YES | YES | YES | YES |
Android Chrome 37 | YES | FUZZY | YES | YES |
<audio>
- サーバ/Proxyの設定によっては
<audio>
が機能しないケースがある(よくある) - iOS デバイスはバージョンアップで
<audio>
の実装が密かに変化している
- サーバ/Proxyの設定によっては
- WebAudio API は、Blob または ArrayBuffer の両方で利用可能 (iOS 6.x を除く)
- iPhone 3GS は 256RAM のため、Web Audio は使わないほうがよい(複数ファイルを扱うとメモリ不足で落ちることも)
- Mac Chrome が優秀なため、全てのシチュエーションで再生できてしまうが、それを鵜呑みにすると、モバイルデバイスで再生されずドハマりする
- Audio/WebAudio 周りの実装は、必ず実機を用意し、実際の動作を確認しながら行うこと
- Tegra 2 SoC 搭載な NEON非搭載のAndroid端末では window.AudioContext がなく、Web Audio は利用できない。あきらめてください
<!DOCTYPE html><html><head><title>test</title>
<meta name="viewport" content="width=device-width, user-scalable=no">
<meta charset="utf-8"></head><body>
<p>1. Audio(url)</p>
<input type="button" value="loadAudio" onclick="loadAudio(0)"></input>
<input type="button" value="playAudio" onclick="playAudio(0)"></input>
<input type="button" value="stopAudio" onclick="stopAudio(0)"></input>
<br />
<br />
<p>2. Audio(arraybuffer)</p>
<input type="button" value="loadAudio" onclick="loadAudio(1)"></input>
<input type="button" value="playAudio" onclick="playAudio(1)"></input>
<input type="button" value="stopAudio" onclick="stopAudio(1)"></input>
<br />
<br />
<p>3. Audio(blob)</p>
<input type="button" value="loadAudio" onclick="loadAudio(2)"></input>
<input type="button" value="playAudio" onclick="playAudio(2)"></input>
<input type="button" value="stopAudio" onclick="stopAudio(2)"></input>
<br />
<br />
<p>4. WebAudio(arraybuffer)</p>
<input type="button" value="loadAudio" onclick="loadAudio(3)"></input>
<input type="button" value="playAudio" onclick="playAudio(3)"></input>
<input type="button" value="stopAudio" onclick="stopAudio(3)"></input>
<br />
<br />
<p>5. WebAudio(blob)</p>
<input type="button" value="loadAudio" onclick="loadAudio(4)"></input>
<input type="button" value="playAudio" onclick="playAudio(4)"></input>
<input type="button" value="stopAudio" onclick="stopAudio(4)"></input>
<br />
<script>
var url1 = "./game.m4a";
var stock = {
0: { node: null, nodeType: "audio", url: url1, responseType: "", mime: "", size: 0, data: null, sound: { canplay: false } },
1: { node: null, nodeType: "audio", url: url1, responseType: "arraybuffer", mime: "", size: 0, data: null, sound: { canplay: false } },
2: { node: null, nodeType: "audio", url: url1, responseType: "blob", mime: "", size: 0, data: null, sound: { canplay: false } },
3: { node: null, nodeType: "webaudio", url: url1, responseType: "arraybuffer", mime: "", size: 0, data: null, sound: { buffer: null, source: null } },
4: { node: null, nodeType: "webaudio", url: url1, responseType: "blob", mime: "", size: 0, data: null, sound: { buffer: null, source: null } },
};
var _AudioContext = window.AudioContext ||
window.webkitAudioContext;
var webAudioContext = _AudioContext ? new _AudioContext()
: null;
function loadAudio(n) {
var target = stock[n];
if (target.responseType) {
_download(target.url, target.responseType, function(data, mime, size) {
target.data = data; // blob or arraybuffer
target.mime = mime;
target.size = size;
_downloaded(n);
});
} else {
_downloaded(n);
}
}
function _download(url, responseType, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
callback(xhr.response,
xhr.getResponseHeader("content-type"),
parseInt(xhr.getResponseHeader("content-length")) || 0);
};
xhr.responseType = responseType;
xhr.open("GET", url);
xhr.send();
}
function _downloaded(n) {
var target = stock[n];
var data = target.data;
if (target.nodeType === "audio") {
var bloburl = "";
if (data instanceof ArrayBuffer) {
bloburl = URL.createObjectURL( new Blob([data], { type: target.mime /* "audio/mp4" */ }) );
} else if (data instanceof Blob) {
bloburl = URL.createObjectURL(data);
} else {
bloburl = target.url;
}
target.node = new Audio();
target.node.src = bloburl;
target.node.volume = 0.2;
target.node.addEventListener("progress", handleEvent, false);
target.node.addEventListener("canplay", handleEvent, false);
target.node.load();
} else {
if (data instanceof ArrayBuffer) {
webAudioContext.decodeAudioData(data, function(decodedBuffer) {
target.sound.buffer = decodedBuffer;
_ready();
});
} else if (data instanceof Blob) {
var reader = new FileReader();
reader.onloadend = function() {
webAudioContext.decodeAudioData(reader.result, function(decodedBuffer) {
target.sound.buffer = decodedBuffer;
_ready();
});
};
reader.readAsArrayBuffer(data);
}
}
function handleEvent(event) {
var duration = target.node.duration;
switch (event.type) {
case "canplay":
target.node.removeEventListener("canplay", handleEvent, false);
target.sound.canplay = true;
break;
case "progress":
break;
}
if (duration > 0 && target.sound.canplay) {
target.node.removeEventListener("progress", handleEvent, false);
_ready();
}
}
}
function playAudio(n) {
var target = stock[n];
if (target.nodeType === "audio") {
target.node.play();
} else if (target.nodeType === "webaudio") {
stopAudio(n);
target.sound.source = webAudioContext.createBufferSource();
target.sound.source.buffer = target.sound.buffer;
target.sound.source.connect(webAudioContext.destination);
if (target.sound.source.start) {
target.sound.source.start(0);
} else {
target.sound.source.noteOn(0); // [iOS 6.x]
}
}
}
function stopAudio(n) {
var target = stock[n];
if (target.nodeType === "audio") {
if (target.node) {
target.node.pause();
}
} else if (target.nodeType === "webaudio") {
if (target.sound.source) {
if (target.sound.source.stop) {
target.sound.source.stop(0);
} else {
target.sound.source.noteOff(0); // [iOS 6.x]
}
target.sound.source = null;
}
}
}
var lime = 80;
function _ready() {
lime += 32;
document.body.style.cssText = "background-color: rgb(0, " + lime + ", 0)";
}
</script>
</body></html>
window.onload のタイミングではサウンドファイルのロードと decodeAudioData によるデコードまで実行可能
var ctx = new AudioContext();
var decodedBuffer = null;
var xhr = new XMLHttpRequest();
xhr.responseType = "arraybuffer";
xhr.onload = function() {
ctx.decodeAudioData(xhr.response, function(buffer) {
decodedBuffer = buffer;
});
};
xhr.open("GET", url);
xhr.send();
一度ユーザアクション由来のイベントハンドラ内で source.start を実行しないと、音が鳴らない
document.body.onclick = function() {
var source = ctx.createBufferSource();
source.buffer = decodedBuffer;
source.connect(ctx.destination);
source.start(0);
};
WebAudio で再生中に、タブの切替やサスペンドでボリューム操作が自動的に行われるかどうかについて調査
タブ切り替え | サスペンド | |
---|---|---|
iPhone 5 (iOS 8) | YES | YES |
iPhone 4S (iOS 7) | YES | |
Nexus 7 (2012)(Android 4.4.4) |