index.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. import { computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch } from "vue";
  2. import styles from "./index.module.less";
  3. import {
  4. getMidiCurrentTime,
  5. getMidiDuration,
  6. handleTogglePlayMidi,
  7. hanldeInitMidiData,
  8. hanldeSetMidiPlaybackRate,
  9. setMidiCurrentTime,
  10. } from "./midiPlayer";
  11. import state, { IPlayState, onEnded, onPlay } from "/src/state";
  12. import { api_playProgress, api_cloudTimeUpdae, api_cloudplayed, api_remove_cloudplayed, api_remove_cloudTimeUpdae } from "/src/helpers/communication";
  13. import { evaluatingData } from "/src/view/evaluating";
  14. import { cloudToggleState } from "/src/helpers/midiPlay"
  15. import { storeData } from "/src/store";
  16. import { handleStartTick } from "../tick";
  17. export const audioData = reactive({
  18. songEle: null as HTMLAudioElement | null, // 原生
  19. backgroundEle: null as HTMLAudioElement | null, //伴唱
  20. mingSongEle: null as HTMLAudioElement | null, //唱名
  21. songCollection: { // 音乐源合集 带节拍器和不带节拍器的
  22. songEle: null as HTMLAudioElement | null,
  23. backgroundEle: null as HTMLAudioElement | null,
  24. fanSongEle: null as HTMLAudioElement | null,
  25. banSongEle: null as HTMLAudioElement | null,
  26. mingSongEle: null as HTMLAudioElement | null,
  27. beatSongEle: null as HTMLAudioElement | null,
  28. betaBackgroundEle: null as HTMLAudioElement | null,
  29. betaFanSongEle: null as HTMLAudioElement | null,
  30. betaBanSongEle: null as HTMLAudioElement | null,
  31. betaMingSongEle: null as HTMLAudioElement | null
  32. },
  33. midiRender: false,
  34. progress: 0, // midi播放进度(单位:秒)
  35. duration: 0, // 音频总时长(单位:秒)
  36. mingSongType: 1 as 0 | 1,
  37. mingSongTypeCollection: {
  38. mingSongEle: null as HTMLAudioElement | null,
  39. mingSongGirlEle: null as HTMLAudioElement | null,
  40. beatMingSongEle: null as HTMLAudioElement | null,
  41. beatMingSongGirlEle: null as HTMLAudioElement | null
  42. }
  43. });
  44. const midiRef = ref();
  45. /** 播放或暂停 */
  46. export const audioListStart = (type: "play" | "paused") => {
  47. // 开始播放之前, 先设置倍数
  48. if (type === "play" && state.originSpeed !== 0) {
  49. const actualRate = state.originAudioPlayRate * state.basePlayRate;
  50. // console.log('音频播放倍率',actualRate)
  51. setAudioPlaybackRate(actualRate);
  52. }
  53. // console.log('api','midi状态1',type,state.isAppPlay)
  54. // 如果是midi播放
  55. if (state.isAppPlay) {
  56. // handleTogglePlayMidi(type);
  57. cloudToggleState(type);
  58. return;
  59. }
  60. if (type === "play") {
  61. if(state.playSource === "mingSong"){
  62. audioData.mingSongEle?.play();
  63. }else{
  64. audioData.songEle?.play();
  65. audioData.backgroundEle?.play();
  66. }
  67. } else if (type === "paused") {
  68. audioData.songEle?.pause();
  69. audioData.backgroundEle?.pause();
  70. audioData.mingSongEle?.pause();
  71. }
  72. };
  73. /** 设置倍数播放 */
  74. export const setAudioPlaybackRate = (rate: number) => {
  75. // 如果是midi播放
  76. if (state.isAppPlay) {
  77. if (state.modeType === "evaluating") return
  78. hanldeSetMidiPlaybackRate(rate);
  79. return;
  80. }
  81. audioData.songEle && (audioData.songEle.playbackRate = rate);
  82. audioData.backgroundEle && (audioData.backgroundEle.playbackRate = rate);
  83. audioData.mingSongEle && (audioData.mingSongEle.playbackRate = rate);
  84. };
  85. /** 获取当前播放的时间 */
  86. export const getAudioCurrentTime = () => {
  87. // 如果是midi播放
  88. if (state.isAppPlay) {
  89. // const c = getMidiCurrentTime();
  90. return audioData.progress;
  91. }
  92. // console.log('返回的时间',state.playSource, audioData.songEle?.currentTime,audioData.progress)
  93. if (state.playSource === "music") return audioData.songEle?.currentTime || audioData.progress;
  94. if (state.playSource === "background") return audioData.backgroundEle?.currentTime || audioData.progress;
  95. if (state.playSource === "mingSong") return audioData.mingSongEle?.currentTime || audioData.progress;
  96. return audioData.songEle?.currentTime || audioData.progress;
  97. };
  98. /** 获取曲谱的总时间 */
  99. export const getAudioDuration = () => {
  100. // 如果是midi播放
  101. if (state.isAppPlay) {
  102. // const d = getMidiDuration();
  103. const songEndTime = state.times[state.times.length - 1 || 0]?.endtime || 0
  104. return audioData.duration || songEndTime;
  105. }
  106. // 唱名文件时长比较短,和普通文件不一样 所以这里单独处理
  107. if(state.playSource === "mingSong") return audioData.mingSongEle?.duration || audioData.duration;
  108. return audioData.songEle?.duration || audioData.backgroundEle?.duration || audioData.mingSongEle?.duration || audioData.duration;
  109. };
  110. /** 设置播放的开始时间 */
  111. export const setAudioCurrentTime = (time: number, index = 0) => {
  112. // console.log('开始时间12345',time)
  113. // 如果是midi播放
  114. if (state.isAppPlay) {
  115. setMidiCurrentTime(index);
  116. return;
  117. }
  118. audioData.songEle && (audioData.songEle.currentTime = time);
  119. audioData.backgroundEle && (audioData.backgroundEle.currentTime = time);
  120. audioData.mingSongEle && (audioData.mingSongEle.currentTime = time);
  121. audioData.progress = time;
  122. };
  123. /** 设置当前没有播放的音频静音 */
  124. export const toggleMutePlayAudio = (source: IPlayState, muted: boolean) => {
  125. if (source === "music") {
  126. if (audioData.songEle) {
  127. audioData.songEle.muted = muted;
  128. }
  129. } else if (source === "background") {
  130. if (audioData.backgroundEle) {
  131. audioData.backgroundEle.muted = muted;
  132. }
  133. } else if(source === "mingSong"){
  134. if (audioData.mingSongEle) {
  135. audioData.mingSongEle.muted = muted;
  136. }
  137. }
  138. };
  139. /** 切换节拍器音源 */
  140. export const changeSongSourceByBate = (isDisBate:boolean) => {
  141. // isDisBate 为true 切换到不带节拍的,为false 切换到带节拍的
  142. let songEleCurrentTime = audioData.songEle?.currentTime || 0
  143. let backgroundEleCurrentTime = audioData.backgroundEle?.currentTime || 0
  144. let mingSongEleCurrentTime = audioData.mingSongEle?.currentTime || 0
  145. // 有一种场景,默认模式没有文件内容的时候,songEle,backgroundEle,mingSongEle都为空,在设置音源之前点击跳转位置,这时候以audioData.progress的时间为准
  146. if(!audioData.songEle&&!audioData.backgroundEle&&!audioData.mingSongEle){
  147. songEleCurrentTime = backgroundEleCurrentTime = mingSongEleCurrentTime = audioData.progress || 0
  148. }
  149. if (isDisBate) {
  150. if(state.playType === "play"){
  151. audioData.songEle = audioData.songCollection.songEle
  152. audioData.backgroundEle = audioData.songCollection.backgroundEle
  153. } else {
  154. audioData.songEle = audioData.songCollection.fanSongEle
  155. audioData.backgroundEle = audioData.songCollection.banSongEle
  156. audioData.mingSongEle = audioData.songCollection.mingSongEle
  157. }
  158. } else {
  159. // 没有节拍器资源的时候 用 不带节拍器的资源,防止播放不了
  160. if(state.playType === "play"){
  161. audioData.songEle = audioData.songCollection.beatSongEle || audioData.songCollection.songEle
  162. audioData.backgroundEle = audioData.songCollection.betaBackgroundEle || audioData.songCollection.backgroundEle
  163. } else {
  164. audioData.songEle = audioData.songCollection.betaFanSongEle || audioData.songCollection.fanSongEle
  165. audioData.backgroundEle = audioData.songCollection.betaBanSongEle || audioData.songCollection.banSongEle
  166. audioData.mingSongEle = audioData.songCollection.betaMingSongEle || audioData.songCollection.mingSongEle
  167. }
  168. }
  169. audioData.songEle && (audioData.songEle.currentTime = songEleCurrentTime)
  170. audioData.backgroundEle && (audioData.backgroundEle.currentTime = backgroundEleCurrentTime)
  171. audioData.mingSongEle && (audioData.mingSongEle.currentTime = mingSongEleCurrentTime)
  172. // 设置静音与取消静音
  173. if (state.playSource === "music") {
  174. audioData.songEle && (audioData.songEle.muted = false);
  175. audioData.backgroundEle && (audioData.backgroundEle.muted = true);
  176. audioData.mingSongEle && (audioData.mingSongEle.muted = true);
  177. } else if (state.playSource === "background") {
  178. audioData.songEle && (audioData.songEle.muted = true);
  179. audioData.backgroundEle && (audioData.backgroundEle.muted = false);
  180. audioData.mingSongEle && (audioData.mingSongEle.muted = true);
  181. }else {
  182. audioData.songEle && (audioData.songEle.muted = true);
  183. audioData.backgroundEle && (audioData.backgroundEle.muted = true);
  184. audioData.mingSongEle && (audioData.mingSongEle.muted = false);
  185. }
  186. }
  187. /** 切换男生女生唱名 */
  188. export const changeMingSongType = () =>{
  189. // 当有男声女声都有值时候才能切换
  190. const { mingSongEle, mingSongGirlEle, beatMingSongEle, beatMingSongGirlEle } = audioData.mingSongTypeCollection
  191. if(mingSongEle&&mingSongGirlEle){
  192. const mingSongType = audioData.mingSongType
  193. audioData.songCollection.mingSongEle = mingSongType === 1 ? mingSongEle : mingSongGirlEle
  194. audioData.songCollection.betaMingSongEle = mingSongType === 1 ? beatMingSongEle : beatMingSongGirlEle
  195. }
  196. }
  197. export default defineComponent({
  198. name: "audio-list",
  199. setup() {
  200. /** iframe 加载完成后, 加载midiURL */
  201. const handleLoad = () => {
  202. midiRef.value.contentWindow.handleRendered = () => {
  203. audioData.midiRender = true
  204. };
  205. hanldeInitMidiData(midiRef.value);
  206. };
  207. watch(
  208. () => state.playSource,
  209. () => {
  210. if (state.modeType === "evaluating" && !state.setting.enableAccompaniment) {
  211. console.log("评测模式设置了关闭伴奏,不切换原音伴奏");
  212. return;
  213. }
  214. if (state.playSource === "music") {
  215. audioData.songEle && (audioData.songEle.muted = false);
  216. audioData.backgroundEle && (audioData.backgroundEle.muted = true);
  217. audioData.mingSongEle && (audioData.mingSongEle.muted = true);
  218. } else if (state.playSource === "background") {
  219. audioData.songEle && (audioData.songEle.muted = true);
  220. audioData.backgroundEle && (audioData.backgroundEle.muted = false);
  221. audioData.mingSongEle && (audioData.mingSongEle.muted = true);
  222. }else {
  223. audioData.songEle && (audioData.songEle.muted = true);
  224. audioData.backgroundEle && (audioData.backgroundEle.muted = true);
  225. audioData.mingSongEle && (audioData.mingSongEle.muted = false);
  226. }
  227. }
  228. );
  229. const createAudio = (src?: string): Promise<HTMLAudioElement | null> => {
  230. if(!src){
  231. return Promise.resolve(null)
  232. }
  233. return new Promise((resolve) => {
  234. const a = new Audio(src + '?v=' + Date.now());
  235. a.load();
  236. a.onloadedmetadata = () => {
  237. resolve(a);
  238. };
  239. a.onerror = () => {
  240. resolve(null);
  241. };
  242. });
  243. };
  244. /**
  245. * #11046
  246. * 声音与圆点消失的节点不一致,可能原因是部分安卓手机没有立即播放,所以需要等待有音频进度返回时再播放节拍器
  247. * mp3节拍器的圆点动画
  248. */
  249. const tickAnimate = (time: number) => {
  250. if (storeData.isApp && state.modeType === 'evaluating' && evaluatingData.needPlayTick && time > 0) {
  251. evaluatingData.needPlayTick = false;
  252. handleStartTick()
  253. }
  254. }
  255. // 监听评测曲谱音频播放进度,返回
  256. const progress = (res: any) => {
  257. const currentTime = res?.currentTime || res?.content?.currentTime;
  258. const total = res?.totalDuration || res?.content?.totalDuration;
  259. const time = currentTime / 1000;
  260. audioData.progress = time;
  261. tickAnimate(time);
  262. // audioData.songEle && (audioData.songEle.currentTime = time);
  263. // audioData.backgroundEle && (audioData.backgroundEle.currentTime = time);
  264. // audioData.mingSongEle && (audioData.mingSongEle.currentTime = time);
  265. audioData.duration = total / 1000;
  266. if (
  267. res?.content?.totalDuration > 1000 &&
  268. currentTime >= total
  269. ) {
  270. if (evaluatingData.isAudioPlayEnd) return
  271. evaluatingData.isAudioPlayEnd = true
  272. onEnded();
  273. }
  274. };
  275. // midi播放进度回调
  276. const midiProgress = (res: any) => {
  277. // console.log('api',res)
  278. if (audioData.duration == 0) {
  279. const songEndTime = state.times[state.times.length - 1 || 0]?.endtime || 0
  280. audioData.duration = songEndTime
  281. }
  282. const currentTime = res?.currentTime || res?.content?.currentTime;
  283. const total = res?.totalDuration || res?.content?.totalDuration;
  284. const time = currentTime / 1000;
  285. audioData.progress = time;
  286. // if (
  287. // audioData.duration > 1000 &&
  288. // currentTime >= audioData.duration * 1000
  289. // ) {
  290. // console.log('midi结束')
  291. // onEnded();
  292. // }
  293. // 选段模式,播放结束
  294. if (state.sectionStatus && state.section.length == 2 && currentTime >= state.section) {
  295. //
  296. }
  297. }
  298. // midi播放结束回调
  299. const midiPlayEnd = (res: any) => {
  300. if (res) {
  301. console.log('midi结束')
  302. audioData.progress = 0
  303. onEnded();
  304. }
  305. }
  306. // 加载音源
  307. function loadAudio(){
  308. return Promise.all([createAudio(state.music), createAudio(state.accompany), createAudio(state.fanSong), createAudio(state.banSong), createAudio(state.mingSong), createAudio(state.mingSongGirl)])
  309. }
  310. // 加载节拍器音源
  311. function loadBeatAudio(){
  312. return Promise.all([createAudio(state.beatSong.music), createAudio(state.beatSong.accompany), createAudio(state.beatSong.fanSong), createAudio(state.beatSong.banSong), createAudio(state.beatSong.mingSong), createAudio(state.beatSong.mingSongGirl)])
  313. }
  314. onMounted(async () => {
  315. // 预览的时候不走音频加载逻辑
  316. if(state.isPreView){
  317. state.isLoading = false;
  318. return
  319. }
  320. if (state.playMode !== "MIDI") {
  321. console.time("音频加载时间")
  322. // 处理音源
  323. const [music, accompany, fanSong, banSong, mingSong, mingSongGirl] = await loadAudio()
  324. audioData.backgroundEle = accompany;
  325. audioData.songEle = music;
  326. Object.assign(audioData.songCollection, {
  327. songEle:music,
  328. backgroundEle:accompany,
  329. fanSongEle:fanSong,
  330. banSongEle:banSong,
  331. mingSongEle:mingSong
  332. })
  333. Object.assign(audioData.mingSongTypeCollection, {
  334. mingSongEle: mingSong,
  335. mingSongGirlEle:mingSongGirl
  336. })
  337. if (music) {
  338. music.addEventListener("play", onPlay);
  339. music.addEventListener("ended", onEnded);
  340. accompany && (accompany.muted = true);
  341. } else if (accompany) {
  342. accompany.addEventListener("play", onPlay);
  343. accompany.addEventListener("ended", onEnded);
  344. }
  345. if(fanSong){
  346. fanSong.addEventListener("play", onPlay);
  347. fanSong.addEventListener("ended", onEnded);
  348. banSong && (banSong.muted = true);
  349. mingSong && (mingSong.muted = true);
  350. }else if(banSong){
  351. banSong.addEventListener("play", onPlay);
  352. banSong.addEventListener("ended", onEnded);
  353. mingSong && (mingSong.muted = true);
  354. }
  355. if(mingSong){
  356. mingSong.addEventListener("play", onPlay);
  357. mingSong.addEventListener("ended", onEnded);
  358. }
  359. if(mingSongGirl){
  360. mingSongGirl.addEventListener("play", onPlay);
  361. mingSongGirl.addEventListener("ended", onEnded);
  362. }
  363. // 处理带节拍器的音源
  364. const [beatMusic, beatAccompany, beatFanSong, beatBanSong, beatMingSong, beatMingSongGirl] = await loadBeatAudio()
  365. Object.assign(audioData.songCollection, {
  366. beatSongEle:beatMusic,
  367. betaBackgroundEle:beatAccompany,
  368. betaFanSongEle:beatFanSong,
  369. betaBanSongEle:beatBanSong,
  370. beatMingSongEle:beatMingSong
  371. })
  372. Object.assign(audioData.mingSongTypeCollection, {
  373. beatMingSongEle: beatMingSong,
  374. beatMingSongGirlEle: beatMingSongGirl
  375. })
  376. if (beatMusic) {
  377. beatMusic.addEventListener("play", onPlay);
  378. beatMusic.addEventListener("ended", onEnded);
  379. beatAccompany && (beatAccompany.muted = true);
  380. } else if (beatAccompany) {
  381. beatAccompany.addEventListener("play", onPlay);
  382. beatAccompany.addEventListener("ended", onEnded);
  383. }
  384. if(beatFanSong){
  385. beatFanSong.addEventListener("play", onPlay);
  386. beatFanSong.addEventListener("ended", onEnded);
  387. beatBanSong && (beatBanSong.muted = true);
  388. beatMingSong && (beatMingSong.muted = true);
  389. }else if(beatBanSong){
  390. beatBanSong.addEventListener("play", onPlay);
  391. beatBanSong.addEventListener("ended", onEnded);
  392. beatMingSong && (beatMingSong.muted = true);
  393. }
  394. if(beatMingSong){
  395. beatMingSong.addEventListener("play", onPlay);
  396. beatMingSong.addEventListener("ended", onEnded);
  397. }
  398. if(beatMingSongGirl){
  399. beatMingSongGirl.addEventListener("play", onPlay);
  400. beatMingSongGirl.addEventListener("ended", onEnded);
  401. }
  402. // 给男声女声赋值
  403. const userGender = storeData.user.gender
  404. // 当不为null 和undefined的时候 取userGender的值
  405. if(userGender!==undefined && userGender!==null){
  406. audioData.mingSongType = userGender
  407. }
  408. // 重新根据性别赋值
  409. changeMingSongType()
  410. state.audioDone = true;
  411. state.isLoading = false
  412. console.timeEnd("音频加载时间")
  413. console.log("音频数据:",audioData)
  414. api_playProgress(progress);
  415. } else {
  416. state.audioDone = true;
  417. state.isLoading = false
  418. const songEndTime = state.times[state.times.length - 1 || 0]?.endtime || 0
  419. audioData.duration = songEndTime
  420. // 监听midi播放进度
  421. api_cloudTimeUpdae(midiProgress);
  422. // 监听midi播放结束
  423. api_cloudplayed(midiPlayEnd);
  424. }
  425. });
  426. onUnmounted(() => {
  427. api_remove_cloudplayed(midiPlayEnd);
  428. api_remove_cloudTimeUpdae(midiProgress);
  429. });
  430. // console.log(state.playMode, state.midiUrl);
  431. return () => (
  432. <>
  433. <div class={styles.audioList}>
  434. {state.playMode === "MIDI" && state.speed != 0 && (
  435. <iframe
  436. style={{ display: "none" }}
  437. ref={midiRef}
  438. src={`/midi/index.html`}
  439. onLoad={handleLoad}
  440. />
  441. )}
  442. </div>
  443. </>
  444. );
  445. },
  446. });