changkejun / hzspkr

Pure JavaScript TTS for Chinese. 纯JavaScript中文语音合成.

Home Page:https://changkejun.github.io/hzspkr/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

求助

watersoft123 opened this issue · comments

你好,上次我给你说的那个五线谱插件abcjs,现在我正在使用,它的音色库是放在服务器上的,我想放到本地离线使用,在安卓中使用httpserver没有问题,但ios上一直不能成功。于是我想着修改一下对音色库文件的请求方式,默认是XHR的http,那么如何不用httpserver直接加载本地音色库?我因为不懂js,你精通js,所以才想到请你帮忙,十分感谢!

下面是请求音色库mpw3文件的核心内容,你打开abcjs文件可以查到

// Load one mp3 file for one note.
// url = the base url for the soundfont
// instrument = the instrument name (e.g. "acoustic_grand_piano")
// name = the pitch name (e.g. "A3")
var soundsCache = webpack_require(/*! ./sounds-cache */ "./src/synth/sounds-cache.js");

var getNote = function getNote(url, instrument, name, audioContext) {
return new Promise(function (resolve, reject) {
if (!soundsCache[instrument]) soundsCache[instrument] = {};
var instrumentCache = soundsCache[instrument];

if (instrumentCache[name] === 'error') {
  return resolve({
    instrument: instrument,
    name: name,
    status: "error",
    message: "Unable to load sound font" + ' ' + url + ' ' + instrument + ' ' + name
  });
}

if (instrumentCache[name] === 'pending') {
  return resolve({
    instrument: instrument,
    name: name,
    status: "pending"
  });
}

if (instrumentCache[name]) {
  return resolve({
    instrument: instrument,
    name: name,
    status: "cached"
  });
} // if (this.debugCallback)
// 	this.debugCallback(`Loading sound: ${instrument} ${name}`);


instrumentCache[name] = "pending"; // This can be called in parallel, so don't call it a second time before the first one has loaded.

var xhr = new XMLHttpRequest();
xhr.open('GET', url  + name + '.mp3', true);
xhr.responseType = 'arraybuffer';
var self = this;

function onSuccess(audioBuffer) {
  instrumentCache[name] = audioBuffer; // if (self.debugCallback)
  // 	self.debugCallback(`Sound loaded: ${instrument} ${name} ${url}`);

  resolve({
    instrument: instrument,
    name: name,
    status: "loaded"
  });
}

function onFailure(error) {
  error = "Can't decode sound. " + url + ' ' + instrument + ' ' + name + ' ' + error;
  if (self.debugCallback) self.debugCallback(error);
  return resolve({
    instrument: instrument,
    name: name,
    status: "error",
    message: error
  });
}

xhr.onload = function (e) {
  if (this.status === 200) {
    try {
      var promise = audioContext.decodeAudioData(this.response, onSuccess, onFailure); // older browsers only have the callback. Newer ones will report an unhandled
      // rejection if catch isn't handled so we need both. We don't need to report it twice, though.

      if (promise && promise["catch"]) promise["catch"](function () {});
    } catch (error) {
      reject(error);
    }
  } else {
    instrumentCache[name] = "error"; // To keep this from trying to load repeatedly.

    var cantLoadMp3 = "Onload error loading sound: " + name + " " + url + " " + e.currentTarget.status + " " + e.currentTarget.statusText;
    if (self.debugCallback) self.debugCallback(cantLoadMp3);
    return resolve({
      instrument: instrument,
      name: name,
      status: "error",
      message: cantLoadMp3
    });
  }
};

xhr.addEventListener("error", function () {
  instrumentCache[name] = "error"; // To keep this from trying to load repeatedly.

  var cantLoadMp3 = "Error in loading sound: " + " " + url;
  if (self.debugCallback) self.debugCallback(cantLoadMp3);
  return resolve({
    instrument: instrument,
    name: name,
    status: "error",
    message: cantLoadMp3
  });
}, false);
xhr.send();

});
};

module.exports = getNote;

xhr.open('GET', url + name + '.mp3', true);
...
function onSuccess(audioBuffer) {
instrumentCache[name] = audioBuffer;
...
这个audioBuffer是个数组,你把它在debug一下,输出到控制台。然后把它拷贝下来。
然后在instrumentCache初始化位置加上,
instrumentCache[“....”]=[...]; 把debug的数组写到这里。这样就不用ajax初始化了。把xhr相关的代码都删除就好了吧。

xhr.open('GET', url + name + '.mp3', true); ... function onSuccess(audioBuffer) { instrumentCache[name] = audioBuffer; ... 这个audioBuffer是个数组,你把它在debug一下,输出到控制台。然后把它拷贝下来。 然后在instrumentCache初始化位置加上, instrumentCache[“....”]=[...]; 把debug的数组写到这里。这样就不用ajax初始化了。把xhr相关的代码都删除就好了吧。

谢谢你这么快回复我,js知识太贫乏了,上网学习才知道js数组还可以以字符串为下标,所以整了一上午都没有成功。我下载了对应acoustic_grand_piano乐器的所有mp3音符文件,可是对这个缓存数组的写法和放置位置还是弄不清楚,总觉得写的怪怪的,注释掉XHR.send后无法播放,去掉注释和缓存数组后播放没问题,这显然写的不对。我上传了可以在浏览器里正常播放的包(ABCjs.zip)和两张截图,麻烦指点迷津,谢谢!

1
2
ABCjs.zip

你先在,function onSuccess(audioBuffer) { 里加上下面的代码,debug出来 audiobuffer。看看这个是什么格式的数组。它输出到浏览器控制台,用F12键可以看到浏览器控制台。
console.log(audioBuffer);

console.log(audioBuffer);

下午有事没来得及弄,刚debug下,得出的结果见图片,好像是audioBuffer的四个属性,但完全不知道如何赋给instrumentCache,对音频处理几乎一窍不通。这样说吧,我从github上把那个对应acoustic_grand_piano的音色库所有mp3文件大概100个全部下载下来了,安卓里开启本地httpserver离线播放没问题。现在的需求是不用开启httpserver如何使用音色库的问题。ABCJS的文档里面确实提到可以离线操作,好像也不用改js,我试过这跟浏览器有关,很容易碰到cors不能播放的问题,在webview里更是不行,因此官方提供的不是可靠的解决方法。
我这边不急,还正在制作赞美诗的abc编码,如果你那天有空了,帮忙看下如何实现。因为国内对zongjiao的东西很敏感,所以想彻底在本地运行。十分感谢!

3

100个mp3有点多。不过也可以。
XMLHttpRequest因为安全性要求只能读http(s)的文件。不能读本地的文件。
如果要读本地文件要用,fetch API。你把你的js文件改成fatch API写法。
fatch API就相当于上传时的操作,所以可以操作本地文件。通过response.arrayBuffer()就可以和你的js的需要的结果匹配了。
下面的例子是日文的。你可以用上面的fatch API关键字查查中文的。
http://once-and-only.com/programing/javascript/local-file%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E3%80%82javascript/

我开始的想法是用base64把文件转成字符串。不如fatch API简单。

100个mp3有点多。不过也可以。 XMLHttpRequest因为安全性要求只能读http(s)的文件。不能读本地的文件。 如果要读本地文件要用,fetch API。你把你的js文件改成fatch API写法。 fatch API就相当于上传时的操作,所以可以操作本地文件。通过response.arrayBuffer()就可以和你的js的需要的结果匹配了。 下面的例子是日文的。你可以用上面的fatch API关键字查查中文的。 http://once-and-only.com/programing/javascript/local-file%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E3%80%82javascript/

我开始的想法是用base64把文件转成字符串。不如fatch API简单。

我试了,最终还是困在了CORS上,我在chrome上安装了cors插件仍然报cors错误;然后在B4A里测试,webview里加上setAllowUniversalAccessFromFileURLs仍是不行,我上网看确实会有这个问题,还是http和file协议的冲突,看来fetch也有问题。算了不折腾了,我先在ios使用在线音色库,等哪天搞到用在ios上的httpserver,再放到本地使用。无论如何也应该感谢你,给了我这么多帮助,也学到了js的不少东西!

我开始的想法是用base64把文件转成字符串。不如fatch API简单。

我试着转成base64放在js里面,过程虽曲折,结果却成功了,不用开服务了,也不用cors处理了。音色库原来大概7M,编码后约10M,我原以为会影响js加载速度,结果跟以前没啥区别,而且播放比http请求似乎更流畅,看不出一点滞后性,目前在浏览器和安卓上都没问题,苹果还没试,至于后期兼容性得看用户的反馈了。再次感谢给予灵感!