[prg] проиграть wav поток с помощью fetch api
здравствуйте.
у меня так и не получилось в событии прогреса получить куски запросов ajax и
обработать для потокового воспроизведения.
оказалось, что на смену или как альтернатива xmlhttprequest пришел fetch
api, который может получать поток как readableStream.
в chrome он работает, а в firefox начиная с версии 57, нужно включить
соответствующую опцию в конфигах
javascript.options.streams;true
после чего, полученный поток можно прочитать, и передать webAudio для
последующего проигрывания.
это все работает, но есть небольшие щелчки при переключении буфферов.
ниже привожу рабочий код.
прозьба, если у кого нибудь есть замечания по поводу работы, или идеи для
улучшения, отписаться.
здесь можно прослушать пример работы
https://ttsapi.gozaltech.org
а тут код.
function wavify(data, numberOfChannels, sampleRate) {
var header = new ArrayBuffer(44);
var d = new DataView(header);
d.setUint8(0, "R".charCodeAt(0));
d.setUint8(1, "I".charCodeAt(0));
d.setUint8(2, "F".charCodeAt(0));
d.setUint8(3, "F".charCodeAt(0));
d.setUint32(4, data.byteLength / 2 + 44, true);
d.setUint8(8, "W".charCodeAt(0));
d.setUint8(9, "A".charCodeAt(0));
d.setUint8(10, "V".charCodeAt(0));
d.setUint8(11, "E".charCodeAt(0));
d.setUint8(12, "f".charCodeAt(0));
d.setUint8(13, "m".charCodeAt(0));
d.setUint8(14, "t".charCodeAt(0));
d.setUint8(15, " ".charCodeAt(0));
d.setUint32(16, 16, true);
d.setUint16(20, 1, true);
d.setUint16(22, numberOfChannels, true);
d.setUint32(24, sampleRate, true);
d.setUint32(28, sampleRate * 1 * 2);
d.setUint16(32, numberOfChannels * 2);
d.setUint16(34, 16, true);
d.setUint8(36, "d".charCodeAt(0));
d.setUint8(37, "a".charCodeAt(0));
d.setUint8(38, "t".charCodeAt(0));
d.setUint8(39, "a".charCodeAt(0));
d.setUint32(40, data.byteLength, true);
return concat(header, data);
};
function concat( buffer1, buffer2 ) {
var tmp = new Uint8Array( buffer1.byteLength + buffer2.byteLength );
tmp.set( new Uint8Array( buffer1 ), 0 );
tmp.set( new Uint8Array( buffer2 ), buffer1.byteLength );
return tmp.buffer;
}
function pad(buffer) {
var currentSample = new Float32Array(1);
buffer.copyFromChannel(currentSample, 0, 0);
let wasPositive = currentSample[0] > 0;
for (let i = 0; i < buffer.length; i += 1) {
buffer.copyFromChannel(currentSample, 0, i);
if ((wasPositive && currentSample[0] < 0) || (!wasPositive &&
currentSample[0] > 0)) {
break;
}
currentSample[0] = 0;
buffer.copyToChannel(currentSample, 0, i);
}
buffer.copyFromChannel(currentSample, 0, buffer.length - 1);
wasPositive = currentSample[0] > 0;
for (let i = buffer.length - 1; i > 0; i -= 1) {
buffer.copyFromChannel(currentSample, 0, i);
if ((wasPositive && currentSample[0] < 0) || (!wasPositive &&
currentSample[0] > 0)) {
break;
}
currentSample[0] = 0;
buffer.copyToChannel(currentSample, 0, i);
}
return buffer;
};
function tts(data) {
context = new (window.AudioContext || window.webkitAudioContext)();
var audioStack = [];
var nextTime = 0;
fetch('/', {
method: 'POST',
body: data
})
.then(function(response) {
var reader = response.body.getReader();
var rest = null;
var isFirstBuffer = true;
var numberOfChannels, sampleRate;
function read() {
return reader.read().then(({ value, done })=> {
var audioBuffer = null;
if (rest !== null) {
audioBuffer = concat(rest,value.buffer);
}else{
audioBuffer = value.buffer;
}
if (isFirstBuffer && audioBuffer.byteLength <= 44) {
rest = audioBuffer;
read();
return;
}
if (isFirstBuffer) {
isFirstBuffer = false;
var dataView = new DataView(audioBuffer);
numberOfChannels = dataView.getUint16(22, true);
sampleRate = dataView.getUint32(24, true);
audioBuffer = audioBuffer.slice(44);
}
if (audioBuffer.byteLength % 2 !== 0) {
rest = audioBuffer.slice(-2, -1);
audioBuffer = audioBuffer.slice(0, -1);
} else {
rest = null;
}
context.decodeAudioData(wavify(audioBuffer, numberOfChannels, sampleRate),
function(buffer) {
audioStack.push(buffer);
if (audioStack.length) {
scheduleBuffers();
}
}, function(err) {
console.log("err(decodeAudioData): "+err);
});
if (done) {
console.log('done');
return;
}
read();
});
}
read();
})
function scheduleBuffers() {
while ( audioStack.length) {
var buffer = audioStack.shift();
var source = context.createBufferSource();
source.buffer = pad(buffer);
source.connect(context.destination);
if (nextTime == 0)
nextTime = context.currentTime + 0.2;
source.start(nextTime);
nextTime += source.buffer.duration;};
}}