J'essaie d'obtenir une capture audio du microphone fonctionnant sous Safari sous iOS11 après la prise en charge du support récemment ajouté
Cependant, le callback onaudioprocess
n'est jamais appelé. Voici un exemple de page:
<html>
<body>
<button onclick="doIt()">DoIt</button>
<ul id="logMessages">
</ul>
<script>
function debug(msg) {
if (typeof msg !== 'undefined') {
var logList = document.getElementById('logMessages');
var newLogItem = document.createElement('li');
if (typeof msg === 'function') {
msg = Function.prototype.toString(msg);
} else if (typeof msg !== 'string') {
msg = JSON.stringify(msg);
}
var newLogText = document.createTextNode(msg);
newLogItem.appendChild(newLogText);
logList.appendChild(newLogItem);
}
}
function doIt() {
var handleSuccess = function (stream) {
var context = new AudioContext();
var input = context.createMediaStreamSource(stream)
var processor = context.createScriptProcessor(1024, 1, 1);
input.connect(processor);
processor.connect(context.destination);
processor.onaudioprocess = function (e) {
// Do something with the data, i.e Convert this to WAV
debug(e.inputBuffer);
};
};
navigator.mediaDevices.getUserMedia({audio: true, video: false})
.then(handleSuccess);
}
</script>
</body>
</html>
Sur la plupart des plates-formes, vous verrez que des éléments sont ajoutés à la liste des messages à l'appel du rappel onaudioprocess
. Cependant, sur iOS, ce rappel n'est jamais appelé.
Y a-t-il autre chose que je devrais faire pour essayer de l'appeler sur iOS 11 avec Safari?
Il y a deux problèmes. Le principal est que Safari sur iOS 11 semble suspendre automatiquement les nouveaux AudioContext
qui ne sont pas créés en réponse à un tapotement. Vous pouvez les resume()
, mais uniquement en réponse à un tapotement.
(Mise à jour: Chrome mobile effectue également cette opération, et Chrome Desktop aura la même limitation à partir de la version 70/décembre 2018.)
Vous devez donc le créer avant d’obtenir le MediaStream
ou bien demander à l’utilisateur de taper à nouveau plus tard.
L'autre problème avec votre code est que AudioContext
est préfixé par webkitAudioContext
dans Safari.
Voici une version de travail:
<html>
<body>
<button onclick="beginAudioCapture()">Begin Audio Capture</button>
<script>
function beginAudioCapture() {
var AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext();
var processor = context.createScriptProcessor(1024, 1, 1);
processor.connect(context.destination);
var handleSuccess = function (stream) {
var input = context.createMediaStreamSource(stream);
input.connect(processor);
var recievedAudio = false;
processor.onaudioprocess = function (e) {
// This will be called multiple times per second.
// The audio data will be in e.inputBuffer
if (!recievedAudio) {
recievedAudio = true;
console.log('got audio', e);
}
};
};
navigator.mediaDevices.getUserMedia({audio: true, video: false})
.then(handleSuccess);
}
</script>
</body>
</html>
(Vous pouvez définir le rappel onaudioprocess
plus tôt, mais vous obtenez des tampons vides jusqu'à ce que l'utilisateur approuve l'accès au microphone.)
Oh, et un autre bogue iOS à surveiller: le Safari sur iPod touch (à partir de iOS 12.1.1) signale qu’il n’a pas de microphone (c’est le cas). Donc, getUserMedia rejettera avec un Error: Invalid constraint
si vous demandez de l'audio à cet endroit.
FYI: Je maintiens le paquet microphone-stream sur NPM qui le fait pour vous et fournit l’audio dans un ReadableStream de style Node.js. Je viens de le mettre à jour avec ce correctif, si vous ou une autre personne préférez l’utiliser par rapport au code brut.
J'ai essayé sur iOS 11.0.1, et malheureusement ce problème n'est toujours pas résolu.
Pour contourner le problème, je me demande s’il est judicieux de remplacer le ScriptProcessor par une fonction qui extrait les données Steam d’un buffet, puis les traite toutes les x millisecondes. Mais c'est un grand changement à la fonctionnalité.