pcm_player.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  3. typeof define === 'function' && define.amd ? define(factory) :
  4. (global = global || self, global.PCMPlayer = factory());
  5. }(this, (function () {
  6. 'use strict';
  7. class PCMPlayer {
  8. constructor(option) {
  9. this.init(option);
  10. }
  11. init(option) {
  12. const defaultOption = {
  13. inputCodec: 'Int16', // 传入的数据是采用多少位编码,默认16位
  14. channels: 1, // 声道数
  15. sampleRate: 8000, // 采样率 单位Hz
  16. flushTime: 1000 // 缓存时间 单位 ms
  17. };
  18. this.option = Object.assign({}, defaultOption, option); // 实例最终配置参数
  19. this.samples = new Float32Array(); // 样本存放区域
  20. this.interval = setInterval(this.flush.bind(this), this.option.flushTime);
  21. this.convertValue = this.getConvertValue();
  22. this.typedArray = this.getTypedArray();
  23. this.initAudioContext();
  24. }
  25. getConvertValue() {
  26. // 根据传入的目标编码位数
  27. // 选定转换数据所需要的基本值
  28. const inputCodecs = {
  29. 'Int8': 128,
  30. 'Int16': 32768,
  31. 'Int32': 2147483648,
  32. 'Float32': 1
  33. };
  34. if (!inputCodecs[this.option.inputCodec]) {
  35. throw new Error('wrong codec.please input one of these codecs:Int8,Int16,Int32,Float32')
  36. }
  37. return inputCodecs[this.option.inputCodec]
  38. }
  39. getTypedArray() {
  40. // 根据传入的目标编码位数
  41. // 选定前端的所需要的保存的二进制数据格式
  42. // 完整TypedArray请看文档
  43. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
  44. const typedArrays = {
  45. 'Int8': Int8Array,
  46. 'Int16': Int16Array,
  47. 'Int32': Int32Array,
  48. 'Float32': Float32Array
  49. };
  50. if (!typedArrays[this.option.inputCodec]) {
  51. throw new Error('wrong codec.please input one of these codecs:Int8,Int16,Int32,Float32')
  52. }
  53. return typedArrays[this.option.inputCodec]
  54. }
  55. initAudioContext() {
  56. // 初始化音频上下文的东西
  57. this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  58. // 控制音量的 GainNode
  59. // https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/createGain
  60. this.gainNode = this.audioCtx.createGain();
  61. this.gainNode.gain.value = 10;
  62. this.gainNode.connect(this.audioCtx.destination);
  63. this.startTime = this.audioCtx.currentTime;
  64. }
  65. static isTypedArray(data) {
  66. // 检测输入的数据是否为 TypedArray 类型或 ArrayBuffer 类型
  67. return (data.byteLength && data.buffer && data.buffer.constructor == ArrayBuffer) || data.constructor == ArrayBuffer;
  68. }
  69. isSupported(data) {
  70. // 数据类型是否支持
  71. // 目前支持 ArrayBuffer 或者 TypedArray
  72. if (!PCMPlayer.isTypedArray(data)) {
  73. throw new Error('请传入ArrayBuffer或者任意TypedArray')
  74. }
  75. return true
  76. }
  77. feed(data) {
  78. this.isSupported(data);
  79. // 获取格式化后的buffer
  80. data = this.getFormatedValue(data);
  81. // 开始拷贝buffer数据
  82. // 新建一个Float32Array的空间
  83. const tmp = new Float32Array(this.samples.length + data.length);
  84. // console.log(data, this.samples, this.samples.length)
  85. // 复制当前的实例的buffer值(历史buff)
  86. // 从头(0)开始复制
  87. tmp.set(this.samples, 0);
  88. // 复制传入的新数据
  89. // 从历史buff位置开始
  90. tmp.set(data, this.samples.length);
  91. // 将新的完整buff数据赋值给samples
  92. // interval定时器也会从samples里面播放数据
  93. this.samples = tmp;
  94. }
  95. getFormatedValue(data) {
  96. if (data.constructor == ArrayBuffer) {
  97. data = new this.typedArray(data);
  98. } else {
  99. data = new this.typedArray(data.buffer);
  100. }
  101. let float32 = new Float32Array(data.length);
  102. for (let i = 0; i < data.length; i++) {
  103. // buffer 缓冲区的数据,需要是IEEE754 里32位的线性PCM,范围从-1到+1
  104. // 所以对数据进行除法
  105. // 除以对应的位数范围,得到-1到+1的数据
  106. // float32[i] = data[i] / 0x8000;
  107. float32[i] = data[i] / this.convertValue;
  108. }
  109. return float32
  110. }
  111. volume(volume) {
  112. this.gainNode.gain.value = volume;
  113. }
  114. destroy() {
  115. if (this.interval) {
  116. clearInterval(this.interval);
  117. }
  118. this.samples = null;
  119. this.audioCtx.close();
  120. this.audioCtx = null;
  121. }
  122. flush() {
  123. if (!this.samples.length) return
  124. var bufferSource = this.audioCtx.createBufferSource();
  125. const length = this.samples.length / this.option.channels;
  126. const audioBuffer = this.audioCtx.createBuffer(this.option.channels, length, this.option.sampleRate);
  127. for (let channel = 0; channel < this.option.channels; channel++) {
  128. const audioData = audioBuffer.getChannelData(channel);
  129. let offset = channel;
  130. let decrement = 50;
  131. for (let i = 0; i < length; i++) {
  132. audioData[i] = this.samples[offset];
  133. /* fadein */
  134. if (i < 50) {
  135. audioData[i] = (audioData[i] * i) / 50;
  136. }
  137. /* fadeout*/
  138. if (i >= (length - 51)) {
  139. audioData[i] = (audioData[i] * decrement--) / 50;
  140. }
  141. offset += this.option.channels;
  142. }
  143. }
  144. if (this.startTime < this.audioCtx.currentTime) {
  145. this.startTime = this.audioCtx.currentTime;
  146. }
  147. // console.log('start vs current ' + this.startTime + ' vs ' + this.audioCtx.currentTime + ' duration: ' + audioBuffer.duration);
  148. bufferSource.buffer = audioBuffer;
  149. bufferSource.connect(this.gainNode);
  150. bufferSource.start(this.startTime);
  151. this.startTime += audioBuffer.duration;
  152. this.samples = new Float32Array();
  153. }
  154. async pause() {
  155. await this.audioCtx.suspend();
  156. }
  157. async continue() {
  158. await this.audioCtx.resume();
  159. }
  160. }
  161. return PCMPlayer;
  162. })));