pcm-player.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. function PCMPlayer(option) {
  2. this.init(option);
  3. }
  4. PCMPlayer.prototype.init = function (option) {
  5. var defaults = {
  6. encoding: '16bitInt',
  7. channels: 1,
  8. sampleRate: 8000,
  9. flushingTime: 1000
  10. };
  11. this.option = Object.assign({}, defaults, option);
  12. this.samples = new Float32Array();
  13. this.flush = this.flush.bind(this);
  14. this.interval = setInterval(this.flush, this.option.flushingTime);
  15. this.maxValue = this.getMaxValue();
  16. this.typedArray = this.getTypedArray();
  17. $.alert({
  18. title: '提示',
  19. text: '开始使用云手机',
  20. onOK: function () {
  21. this.createContext();
  22. }
  23. });
  24. };
  25. PCMPlayer.prototype.getMaxValue = function () {
  26. var encodings = {
  27. '8bitInt': 128,
  28. '16bitInt': 32768,
  29. '32bitInt': 2147483648,
  30. '32bitFloat': 1
  31. }
  32. return encodings[this.option.encoding] ? encodings[this.option.encoding] : encodings['16bitInt'];
  33. };
  34. PCMPlayer.prototype.getTypedArray = function () {
  35. var typedArrays = {
  36. '8bitInt': Int8Array,
  37. '16bitInt': Int16Array,
  38. '32bitInt': Int32Array,
  39. '32bitFloat': Float32Array
  40. }
  41. return typedArrays[this.option.encoding] ? typedArrays[this.option.encoding] : typedArrays['16bitInt'];
  42. };
  43. PCMPlayer.prototype.createContext = function () {
  44. var handleSuccess = function (stream) {
  45. this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  46. this.gainNode = this.audioCtx.createGain();
  47. this.gainNode.gain.value = 1;
  48. this.gainNode.connect(this.audioCtx.destination);
  49. this.startTime = this.audioCtx.currentTime;
  50. }
  51. navigator.mediaDevices.getUserMedia({audio: true, video: true}).then(handleSuccess);
  52. };
  53. PCMPlayer.prototype.isTypedArray = function (data) {
  54. return (data.byteLength && data.buffer && data.buffer.constructor == ArrayBuffer);
  55. };
  56. PCMPlayer.prototype.feed = function (data) {
  57. if (!this.isTypedArray(data)) return;
  58. data = this.getFormatedValue(data);
  59. var tmp = new Float32Array(this.samples.length + data.length);
  60. tmp.set(this.samples, 0);
  61. tmp.set(data, this.samples.length);
  62. this.samples = tmp;
  63. };
  64. PCMPlayer.prototype.getFormatedValue = function (data) {
  65. var data = new this.typedArray(data.buffer),
  66. float32 = new Float32Array(data.length),
  67. i;
  68. for (i = 0; i < data.length; i++) {
  69. float32[i] = data[i] / this.maxValue;
  70. }
  71. return float32;
  72. };
  73. PCMPlayer.prototype.volume = function (volume) {
  74. this.gainNode.gain.value = volume;
  75. };
  76. PCMPlayer.prototype.destroy = function () {
  77. if (this.interval) {
  78. clearInterval(this.interval);
  79. }
  80. this.samples = null;
  81. this.audioCtx.close();
  82. this.audioCtx = null;
  83. };
  84. PCMPlayer.prototype.flush = function () {
  85. if (!this.samples.length) return;
  86. var bufferSource = this.audioCtx.createBufferSource(),
  87. length = this.samples.length / this.option.channels,
  88. audioBuffer = this.audioCtx.createBuffer(this.option.channels, length, this.option.sampleRate),
  89. audioData,
  90. channel,
  91. offset,
  92. i,
  93. decrement;
  94. for (channel = 0; channel < this.option.channels; channel++) {
  95. audioData = audioBuffer.getChannelData(channel);
  96. offset = channel;
  97. decrement = 50;
  98. for (i = 0; i < length; i++) {
  99. audioData[i] = this.samples[offset];
  100. /* fadein */
  101. if (i < 50) {
  102. audioData[i] = (audioData[i] * i) / 50;
  103. }
  104. /* fadeout*/
  105. if (i >= (length - 51)) {
  106. audioData[i] = (audioData[i] * decrement--) / 50;
  107. }
  108. offset += this.option.channels;
  109. }
  110. }
  111. if (this.startTime < this.audioCtx.currentTime) {
  112. this.startTime = this.audioCtx.currentTime;
  113. }
  114. bufferSource.buffer = audioBuffer;
  115. bufferSource.connect(this.gainNode);
  116. bufferSource.start(this.startTime);
  117. this.startTime += audioBuffer.duration;
  118. this.samples = new Float32Array();
  119. };