You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Introduce Plugins (#13836) Signed-off-by: yihong0618 <zouzou0208@gmail.com> Signed-off-by: -LAN- <laipz8200@outlook.com> Signed-off-by: xhe <xw897002528@gmail.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: takatost <takatost@gmail.com> Co-authored-by: kurokobo <kuro664@gmail.com> Co-authored-by: Novice Lee <novicelee@NoviPro.local> Co-authored-by: zxhlyh <jasonapring2015@outlook.com> Co-authored-by: AkaraChen <akarachen@outlook.com> Co-authored-by: Yi <yxiaoisme@gmail.com> Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: JzoNg <jzongcode@gmail.com> Co-authored-by: twwu <twwu@dify.ai> Co-authored-by: Hiroshi Fujita <fujita-h@users.noreply.github.com> Co-authored-by: AkaraChen <85140972+AkaraChen@users.noreply.github.com> Co-authored-by: NFish <douxc512@gmail.com> Co-authored-by: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Co-authored-by: 非法操作 <hjlarry@163.com> Co-authored-by: Novice <857526207@qq.com> Co-authored-by: Hiroki Nagai <82458324+nagaihiroki-git@users.noreply.github.com> Co-authored-by: Gen Sato <52241300+halogen22@users.noreply.github.com> Co-authored-by: eux <euxuuu@gmail.com> Co-authored-by: huangzhuo1949 <167434202+huangzhuo1949@users.noreply.github.com> Co-authored-by: huangzhuo <huangzhuo1@xiaomi.com> Co-authored-by: lotsik <lotsik@mail.ru> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: nite-knite <nkCoding@gmail.com> Co-authored-by: Jyong <76649700+JohnJyong@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: gakkiyomi <gakkiyomi@aliyun.com> Co-authored-by: CN-P5 <heibai2006@gmail.com> Co-authored-by: CN-P5 <heibai2006@qq.com> Co-authored-by: Chuehnone <1897025+chuehnone@users.noreply.github.com> Co-authored-by: yihong <zouzou0208@gmail.com> Co-authored-by: Kevin9703 <51311316+Kevin9703@users.noreply.github.com> Co-authored-by: -LAN- <laipz8200@outlook.com> Co-authored-by: Boris Feld <lothiraldan@gmail.com> Co-authored-by: mbo <himabo@gmail.com> Co-authored-by: mabo <mabo@aeyes.ai> Co-authored-by: Warren Chen <warren.chen830@gmail.com> Co-authored-by: JzoNgKVO <27049666+JzoNgKVO@users.noreply.github.com> Co-authored-by: jiandanfeng <chenjh3@wangsu.com> Co-authored-by: zhu-an <70234959+xhdd123321@users.noreply.github.com> Co-authored-by: zhaoqingyu.1075 <zhaoqingyu.1075@bytedance.com> Co-authored-by: 海狸大師 <86974027+yenslife@users.noreply.github.com> Co-authored-by: Xu Song <xusong.vip@gmail.com> Co-authored-by: rayshaw001 <396301947@163.com> Co-authored-by: Ding Jiatong <dingjiatong@gmail.com> Co-authored-by: Bowen Liang <liangbowen@gf.com.cn> Co-authored-by: JasonVV <jasonwangiii@outlook.com> Co-authored-by: le0zh <newlight@qq.com> Co-authored-by: zhuxinliang <zhuxinliang@didiglobal.com> Co-authored-by: k-zaku <zaku99@outlook.jp> Co-authored-by: luckylhb90 <luckylhb90@gmail.com> Co-authored-by: hobo.l <hobo.l@binance.com> Co-authored-by: jiangbo721 <365065261@qq.com> Co-authored-by: 刘江波 <jiangbo721@163.com> Co-authored-by: Shun Miyazawa <34241526+miya@users.noreply.github.com> Co-authored-by: EricPan <30651140+Egfly@users.noreply.github.com> Co-authored-by: crazywoola <427733928@qq.com> Co-authored-by: sino <sino2322@gmail.com> Co-authored-by: Jhvcc <37662342+Jhvcc@users.noreply.github.com> Co-authored-by: lowell <lowell.hu@zkteco.in> Co-authored-by: Boris Polonsky <BorisPolonsky@users.noreply.github.com> Co-authored-by: Ademílson Tonato <ademilsonft@outlook.com> Co-authored-by: Ademílson Tonato <ademilson.tonato@refurbed.com> Co-authored-by: IWAI, Masaharu <iwaim.sub@gmail.com> Co-authored-by: Yueh-Po Peng (Yabi) <94939112+y10ab1@users.noreply.github.com> Co-authored-by: Jason <ggbbddjm@gmail.com> Co-authored-by: Xin Zhang <sjhpzx@gmail.com> Co-authored-by: yjc980121 <3898524+yjc980121@users.noreply.github.com> Co-authored-by: heyszt <36215648+hieheihei@users.noreply.github.com> Co-authored-by: Abdullah AlOsaimi <osaimiacc@gmail.com> Co-authored-by: Abdullah AlOsaimi <189027247+osaimi@users.noreply.github.com> Co-authored-by: Yingchun Lai <laiyingchun@apache.org> Co-authored-by: Hash Brown <hi@xzd.me> Co-authored-by: zuodongxu <192560071+zuodongxu@users.noreply.github.com> Co-authored-by: Masashi Tomooka <tmokmss@users.noreply.github.com> Co-authored-by: aplio <ryo.091219@gmail.com> Co-authored-by: Obada Khalili <54270856+obadakhalili@users.noreply.github.com> Co-authored-by: Nam Vu <zuzoovn@gmail.com> Co-authored-by: Kei YAMAZAKI <1715090+kei-yamazaki@users.noreply.github.com> Co-authored-by: TechnoHouse <13776377+deephbz@users.noreply.github.com> Co-authored-by: Riddhimaan-Senapati <114703025+Riddhimaan-Senapati@users.noreply.github.com> Co-authored-by: MaFee921 <31881301+2284730142@users.noreply.github.com> Co-authored-by: te-chan <t-nakanome@sakura-is.co.jp> Co-authored-by: HQidea <HQidea@users.noreply.github.com> Co-authored-by: Joshbly <36315710+Joshbly@users.noreply.github.com> Co-authored-by: xhe <xw897002528@gmail.com> Co-authored-by: weiwenyan-dev <154779315+weiwenyan-dev@users.noreply.github.com> Co-authored-by: ex_wenyan.wei <ex_wenyan.wei@tcl.com> Co-authored-by: engchina <12236799+engchina@users.noreply.github.com> Co-authored-by: engchina <atjapan2015@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: 呆萌闷油瓶 <253605712@qq.com> Co-authored-by: Kemal <kemalmeler@outlook.com> Co-authored-by: Lazy_Frog <4590648+lazyFrogLOL@users.noreply.github.com> Co-authored-by: Yi Xiao <54782454+YIXIAO0@users.noreply.github.com> Co-authored-by: Steven sun <98230804+Tuyohai@users.noreply.github.com> Co-authored-by: steven <sunzwj@digitalchina.com> Co-authored-by: Kalo Chin <91766386+fdb02983rhy@users.noreply.github.com> Co-authored-by: Katy Tao <34019945+KatyTao@users.noreply.github.com> Co-authored-by: depy <42985524+h4ckdepy@users.noreply.github.com> Co-authored-by: 胡春东 <gycm520@gmail.com> Co-authored-by: Junjie.M <118170653@qq.com> Co-authored-by: MuYu <mr.muzea@gmail.com> Co-authored-by: Naoki Takashima <39912547+takatea@users.noreply.github.com> Co-authored-by: Summer-Gu <37869445+gubinjie@users.noreply.github.com> Co-authored-by: Fei He <droxer.he@gmail.com> Co-authored-by: ybalbert001 <120714773+ybalbert001@users.noreply.github.com> Co-authored-by: Yuanbo Li <ybalbert@amazon.com> Co-authored-by: douxc <7553076+douxc@users.noreply.github.com> Co-authored-by: liuzhenghua <1090179900@qq.com> Co-authored-by: Wu Jiayang <62842862+Wu-Jiayang@users.noreply.github.com> Co-authored-by: Your Name <you@example.com> Co-authored-by: kimjion <45935338+kimjion@users.noreply.github.com> Co-authored-by: AugNSo <song.tiankai@icloud.com> Co-authored-by: llinvokerl <38915183+llinvokerl@users.noreply.github.com> Co-authored-by: liusurong.lsr <liusurong.lsr@alibaba-inc.com> Co-authored-by: Vasu Negi <vasu-negi@users.noreply.github.com> Co-authored-by: Hundredwz <1808096180@qq.com> Co-authored-by: Xiyuan Chen <52963600+GareArc@users.noreply.github.com>
8 月之前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. import Toast from '@/app/components/base/toast'
  2. import { textToAudioStream } from '@/service/share'
  3. declare global {
  4. // eslint-disable-next-line ts/consistent-type-definitions
  5. interface Window {
  6. ManagedMediaSource: any
  7. }
  8. }
  9. export default class AudioPlayer {
  10. mediaSource: MediaSource | null
  11. audio: HTMLAudioElement
  12. audioContext: AudioContext
  13. sourceBuffer?: any
  14. cacheBuffers: ArrayBuffer[] = []
  15. pauseTimer: number | null = null
  16. msgId: string | undefined
  17. msgContent: string | null | undefined = null
  18. voice: string | undefined = undefined
  19. isLoadData = false
  20. url: string
  21. isPublic: boolean
  22. callback: ((event: string) => void) | null
  23. constructor(streamUrl: string, isPublic: boolean, msgId: string | undefined, msgContent: string | null | undefined, voice: string | undefined, callback: ((event: string) => void) | null) {
  24. this.audioContext = new AudioContext()
  25. this.msgId = msgId
  26. this.msgContent = msgContent
  27. this.url = streamUrl
  28. this.isPublic = isPublic
  29. this.voice = voice
  30. this.callback = callback
  31. // Compatible with iphone ios17 ManagedMediaSource
  32. const MediaSource = window.ManagedMediaSource || window.MediaSource
  33. if (!MediaSource) {
  34. Toast.notify({
  35. message: 'Your browser does not support audio streaming, if you are using an iPhone, please update to iOS 17.1 or later.',
  36. type: 'error',
  37. })
  38. }
  39. this.mediaSource = MediaSource ? new MediaSource() : null
  40. this.audio = new Audio()
  41. this.setCallback(callback)
  42. if (!window.MediaSource) { // if use ManagedMediaSource
  43. this.audio.disableRemotePlayback = true
  44. this.audio.controls = true
  45. }
  46. this.audio.src = this.mediaSource ? URL.createObjectURL(this.mediaSource) : ''
  47. this.audio.autoplay = true
  48. const source = this.audioContext.createMediaElementSource(this.audio)
  49. source.connect(this.audioContext.destination)
  50. this.listenMediaSource('audio/mpeg')
  51. }
  52. public resetMsgId(msgId: string) {
  53. this.msgId = msgId
  54. }
  55. private listenMediaSource(contentType: string) {
  56. this.mediaSource?.addEventListener('sourceopen', () => {
  57. if (this.sourceBuffer)
  58. return
  59. this.sourceBuffer = this.mediaSource?.addSourceBuffer(contentType)
  60. })
  61. }
  62. public setCallback(callback: ((event: string) => void) | null) {
  63. this.callback = callback
  64. if (callback) {
  65. this.audio.addEventListener('ended', () => {
  66. callback('ended')
  67. }, false)
  68. this.audio.addEventListener('paused', () => {
  69. callback('paused')
  70. }, true)
  71. this.audio.addEventListener('loaded', () => {
  72. callback('loaded')
  73. }, true)
  74. this.audio.addEventListener('play', () => {
  75. callback('play')
  76. }, true)
  77. this.audio.addEventListener('timeupdate', () => {
  78. callback('timeupdate')
  79. }, true)
  80. this.audio.addEventListener('loadeddate', () => {
  81. callback('loadeddate')
  82. }, true)
  83. this.audio.addEventListener('canplay', () => {
  84. callback('canplay')
  85. }, true)
  86. this.audio.addEventListener('error', () => {
  87. callback('error')
  88. }, true)
  89. }
  90. }
  91. private async loadAudio() {
  92. try {
  93. const audioResponse: any = await textToAudioStream(this.url, this.isPublic, { content_type: 'audio/mpeg' }, {
  94. message_id: this.msgId,
  95. streaming: true,
  96. voice: this.voice,
  97. text: this.msgContent,
  98. })
  99. if (audioResponse.status !== 200) {
  100. this.isLoadData = false
  101. if (this.callback)
  102. this.callback('error')
  103. }
  104. const reader = audioResponse.body.getReader()
  105. while (true) {
  106. const { value, done } = await reader.read()
  107. if (done) {
  108. this.receiveAudioData(value)
  109. break
  110. }
  111. this.receiveAudioData(value)
  112. }
  113. }
  114. catch {
  115. this.isLoadData = false
  116. this.callback && this.callback('error')
  117. }
  118. }
  119. // play audio
  120. public playAudio() {
  121. if (this.isLoadData) {
  122. if (this.audioContext.state === 'suspended') {
  123. this.audioContext.resume().then((_) => {
  124. this.audio.play()
  125. this.callback && this.callback('play')
  126. })
  127. }
  128. else if (this.audio.ended) {
  129. this.audio.play()
  130. this.callback && this.callback('play')
  131. }
  132. if (this.callback)
  133. this.callback('play')
  134. }
  135. else {
  136. this.isLoadData = true
  137. this.loadAudio()
  138. }
  139. }
  140. private theEndOfStream() {
  141. const endTimer = setInterval(() => {
  142. if (!this.sourceBuffer?.updating) {
  143. this.mediaSource?.endOfStream()
  144. clearInterval(endTimer)
  145. }
  146. }, 10)
  147. }
  148. private finishStream() {
  149. const timer = setInterval(() => {
  150. if (!this.cacheBuffers.length) {
  151. this.theEndOfStream()
  152. clearInterval(timer)
  153. }
  154. if (this.cacheBuffers.length && !this.sourceBuffer?.updating) {
  155. const arrayBuffer = this.cacheBuffers.shift()!
  156. this.sourceBuffer?.appendBuffer(arrayBuffer)
  157. }
  158. }, 10)
  159. }
  160. public async playAudioWithAudio(audio: string, play = true) {
  161. if (!audio || !audio.length) {
  162. this.finishStream()
  163. return
  164. }
  165. const audioContent = Buffer.from(audio, 'base64')
  166. this.receiveAudioData(new Uint8Array(audioContent))
  167. if (play) {
  168. this.isLoadData = true
  169. if (this.audio.paused) {
  170. this.audioContext.resume().then((_) => {
  171. this.audio.play()
  172. this.callback && this.callback('play')
  173. })
  174. }
  175. else if (this.audio.ended) {
  176. this.audio.play()
  177. this.callback && this.callback('play')
  178. }
  179. else if (this.audio.played) { /* empty */ }
  180. else {
  181. this.audio.play()
  182. this.callback && this.callback('play')
  183. }
  184. }
  185. }
  186. public pauseAudio() {
  187. this.callback && this.callback('paused')
  188. this.audio.pause()
  189. this.audioContext.suspend()
  190. }
  191. private receiveAudioData(unit8Array: Uint8Array) {
  192. if (!unit8Array) {
  193. this.finishStream()
  194. return
  195. }
  196. const audioData = this.byteArrayToArrayBuffer(unit8Array)
  197. if (!audioData.byteLength) {
  198. if (this.mediaSource?.readyState === 'open')
  199. this.finishStream()
  200. return
  201. }
  202. if (this.sourceBuffer?.updating) {
  203. this.cacheBuffers.push(audioData)
  204. }
  205. else {
  206. if (this.cacheBuffers.length && !this.sourceBuffer?.updating) {
  207. this.cacheBuffers.push(audioData)
  208. const cacheBuffer = this.cacheBuffers.shift()!
  209. this.sourceBuffer?.appendBuffer(cacheBuffer)
  210. }
  211. else {
  212. this.sourceBuffer?.appendBuffer(audioData)
  213. }
  214. }
  215. }
  216. private byteArrayToArrayBuffer(byteArray: Uint8Array): ArrayBuffer {
  217. const arrayBuffer = new ArrayBuffer(byteArray.length)
  218. const uint8Array = new Uint8Array(arrayBuffer)
  219. uint8Array.set(byteArray)
  220. return arrayBuffer
  221. }
  222. }