index.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  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 || state.modeType === 'evaluating') {
  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. const currentTime = audioData.songEle?.currentTime || audioData.backgroundEle?.currentTime || audioData.mingSongEle?.currentTime || audioData.progress || 0
  143. if (isDisBate) {
  144. if(state.playType === "play"){
  145. audioData.songEle = audioData.songCollection.songEle
  146. audioData.backgroundEle = audioData.songCollection.backgroundEle
  147. } else {
  148. audioData.songEle = audioData.songCollection.fanSongEle
  149. audioData.backgroundEle = audioData.songCollection.banSongEle
  150. audioData.mingSongEle = audioData.songCollection.mingSongEle
  151. }
  152. } else {
  153. // 没有节拍器资源的时候 用 不带节拍器的资源,防止播放不了
  154. if(state.playType === "play"){
  155. audioData.songEle = audioData.songCollection.beatSongEle || audioData.songCollection.songEle
  156. audioData.backgroundEle = audioData.songCollection.betaBackgroundEle || audioData.songCollection.backgroundEle
  157. } else {
  158. audioData.songEle = audioData.songCollection.betaFanSongEle || audioData.songCollection.fanSongEle
  159. audioData.backgroundEle = audioData.songCollection.betaBanSongEle || audioData.songCollection.banSongEle
  160. audioData.mingSongEle = audioData.songCollection.betaMingSongEle || audioData.songCollection.mingSongEle
  161. }
  162. }
  163. audioData.songEle && (audioData.songEle.currentTime = currentTime)
  164. audioData.backgroundEle && (audioData.backgroundEle.currentTime = currentTime)
  165. audioData.mingSongEle && (audioData.mingSongEle.currentTime = currentTime)
  166. // 设置静音与取消静音
  167. if (state.playSource === "music") {
  168. audioData.songEle && (audioData.songEle.muted = false);
  169. audioData.backgroundEle && (audioData.backgroundEle.muted = true);
  170. audioData.mingSongEle && (audioData.mingSongEle.muted = true);
  171. } else if (state.playSource === "background") {
  172. audioData.songEle && (audioData.songEle.muted = true);
  173. audioData.backgroundEle && (audioData.backgroundEle.muted = false);
  174. audioData.mingSongEle && (audioData.mingSongEle.muted = true);
  175. }else {
  176. audioData.songEle && (audioData.songEle.muted = true);
  177. audioData.backgroundEle && (audioData.backgroundEle.muted = true);
  178. audioData.mingSongEle && (audioData.mingSongEle.muted = false);
  179. }
  180. }
  181. /** 切换男生女生唱名 */
  182. export const changeMingSongType = () =>{
  183. // 当有男声女声都有值时候才能切换
  184. const { mingSongEle, mingSongGirlEle, beatMingSongEle, beatMingSongGirlEle } = audioData.mingSongTypeCollection
  185. if(mingSongEle&&mingSongGirlEle){
  186. const mingSongType = audioData.mingSongType
  187. audioData.songCollection.mingSongEle = mingSongType === 1 ? mingSongEle : mingSongGirlEle
  188. audioData.songCollection.betaMingSongEle = mingSongType === 1 ? beatMingSongEle : beatMingSongGirlEle
  189. }
  190. }
  191. export default defineComponent({
  192. name: "audio-list",
  193. setup() {
  194. /** iframe 加载完成后, 加载midiURL */
  195. const handleLoad = () => {
  196. midiRef.value.contentWindow.handleRendered = () => {
  197. audioData.midiRender = true
  198. };
  199. hanldeInitMidiData(midiRef.value);
  200. };
  201. watch(
  202. () => state.playSource,
  203. () => {
  204. if (state.modeType === "evaluating" && !state.setting.enableAccompaniment) {
  205. console.log("评测模式设置了关闭伴奏,不切换原音伴奏");
  206. return;
  207. }
  208. if (state.playSource === "music") {
  209. audioData.songEle && (audioData.songEle.muted = false);
  210. audioData.backgroundEle && (audioData.backgroundEle.muted = true);
  211. audioData.mingSongEle && (audioData.mingSongEle.muted = true);
  212. } else if (state.playSource === "background") {
  213. audioData.songEle && (audioData.songEle.muted = true);
  214. audioData.backgroundEle && (audioData.backgroundEle.muted = false);
  215. audioData.mingSongEle && (audioData.mingSongEle.muted = true);
  216. }else {
  217. audioData.songEle && (audioData.songEle.muted = true);
  218. audioData.backgroundEle && (audioData.backgroundEle.muted = true);
  219. audioData.mingSongEle && (audioData.mingSongEle.muted = false);
  220. }
  221. }
  222. );
  223. const createAudio = (src?: string): Promise<HTMLAudioElement | null> => {
  224. if(!src){
  225. return Promise.resolve(null)
  226. }
  227. return new Promise((resolve) => {
  228. const a = new Audio(src + '?v=' + Date.now());
  229. a.load();
  230. a.onloadedmetadata = () => {
  231. resolve(a);
  232. };
  233. a.onerror = () => {
  234. resolve(null);
  235. };
  236. });
  237. };
  238. /**
  239. * #11046
  240. * 声音与圆点消失的节点不一致,可能原因是部分安卓手机没有立即播放,所以需要等待有音频进度返回时再播放节拍器
  241. * mp3节拍器的圆点动画
  242. */
  243. const tickAnimate = (time: number) => {
  244. if (storeData.isApp && state.modeType === 'evaluating' && evaluatingData.needPlayTick && time > 0) {
  245. evaluatingData.needPlayTick = false;
  246. handleStartTick()
  247. }
  248. }
  249. // 监听评测曲谱音频播放进度,返回
  250. const progress = (res: any) => {
  251. const currentTime = res?.currentTime || res?.content?.currentTime;
  252. const total = res?.totalDuration || res?.content?.totalDuration;
  253. const time = currentTime / 1000;
  254. audioData.progress = time;
  255. tickAnimate(time);
  256. // audioData.songEle && (audioData.songEle.currentTime = time);
  257. // audioData.backgroundEle && (audioData.backgroundEle.currentTime = time);
  258. // audioData.mingSongEle && (audioData.mingSongEle.currentTime = time);
  259. audioData.duration = total / 1000;
  260. if (
  261. res?.content?.totalDuration > 1000 &&
  262. currentTime >= total
  263. ) {
  264. console.log('播放结束1111',evaluatingData.isAudioPlayEnd,currentTime,total)
  265. if (evaluatingData.isAudioPlayEnd) return
  266. evaluatingData.isAudioPlayEnd = true
  267. onEnded();
  268. }
  269. };
  270. // midi播放进度回调
  271. const midiProgress = (res: any) => {
  272. // console.log('api',res)
  273. if (audioData.duration == 0) {
  274. const songEndTime = state.times[state.times.length - 1 || 0]?.endtime || 0
  275. audioData.duration = songEndTime
  276. }
  277. const currentTime = res?.currentTime || res?.content?.currentTime;
  278. const total = res?.totalDuration || res?.content?.totalDuration;
  279. const time = currentTime / 1000;
  280. audioData.progress = time;
  281. // if (
  282. // audioData.duration > 1000 &&
  283. // currentTime >= audioData.duration * 1000
  284. // ) {
  285. // console.log('midi结束')
  286. // onEnded();
  287. // }
  288. // 选段模式,播放结束
  289. if (state.sectionStatus && state.section.length == 2 && currentTime >= state.section) {
  290. //
  291. }
  292. }
  293. // midi播放结束回调
  294. const midiPlayEnd = (res: any) => {
  295. if (res) {
  296. console.log('midi结束')
  297. audioData.progress = 0
  298. onEnded();
  299. }
  300. }
  301. // 加载音源
  302. function loadAudio(){
  303. return Promise.all([createAudio(state.music), createAudio(state.accompany), createAudio(state.fanSong), createAudio(state.banSong), createAudio(state.mingSong), createAudio(state.mingSongGirl)])
  304. }
  305. // 加载节拍器音源
  306. function loadBeatAudio(){
  307. 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)])
  308. }
  309. onMounted(async () => {
  310. // 预览的时候不走音频加载逻辑
  311. if(state.isPreView){
  312. state.isLoading = false;
  313. return
  314. }
  315. if (state.playMode !== "MIDI") {
  316. console.time("音频加载时间")
  317. // 处理音源
  318. const [music, accompany, fanSong, banSong, mingSong, mingSongGirl] = await loadAudio()
  319. audioData.backgroundEle = accompany;
  320. audioData.songEle = music;
  321. Object.assign(audioData.songCollection, {
  322. songEle:music,
  323. backgroundEle:accompany,
  324. fanSongEle:fanSong,
  325. banSongEle:banSong,
  326. mingSongEle:mingSong
  327. })
  328. Object.assign(audioData.mingSongTypeCollection, {
  329. mingSongEle: mingSong,
  330. mingSongGirlEle:mingSongGirl
  331. })
  332. if (music) {
  333. music.addEventListener("play", onPlay);
  334. music.addEventListener("ended", onEnded);
  335. accompany && (accompany.muted = true);
  336. } else if (accompany) {
  337. accompany.addEventListener("play", onPlay);
  338. accompany.addEventListener("ended", onEnded);
  339. }
  340. if(fanSong){
  341. fanSong.addEventListener("play", onPlay);
  342. fanSong.addEventListener("ended", onEnded);
  343. banSong && (banSong.muted = true);
  344. mingSong && (mingSong.muted = true);
  345. }else if(banSong){
  346. banSong.addEventListener("play", onPlay);
  347. banSong.addEventListener("ended", onEnded);
  348. mingSong && (mingSong.muted = true);
  349. }
  350. if(mingSong){
  351. mingSong.addEventListener("play", onPlay);
  352. mingSong.addEventListener("ended", onEnded);
  353. }
  354. if(mingSongGirl){
  355. mingSongGirl.addEventListener("play", onPlay);
  356. mingSongGirl.addEventListener("ended", onEnded);
  357. }
  358. // 处理带节拍器的音源
  359. const [beatMusic, beatAccompany, beatFanSong, beatBanSong, beatMingSong, beatMingSongGirl] = await loadBeatAudio()
  360. Object.assign(audioData.songCollection, {
  361. beatSongEle:beatMusic,
  362. betaBackgroundEle:beatAccompany,
  363. betaFanSongEle:beatFanSong,
  364. betaBanSongEle:beatBanSong,
  365. beatMingSongEle:beatMingSong
  366. })
  367. Object.assign(audioData.mingSongTypeCollection, {
  368. beatMingSongEle: beatMingSong,
  369. beatMingSongGirlEle: beatMingSongGirl
  370. })
  371. if (beatMusic) {
  372. beatMusic.addEventListener("play", onPlay);
  373. beatMusic.addEventListener("ended", onEnded);
  374. beatAccompany && (beatAccompany.muted = true);
  375. } else if (beatAccompany) {
  376. beatAccompany.addEventListener("play", onPlay);
  377. beatAccompany.addEventListener("ended", onEnded);
  378. }
  379. if(beatFanSong){
  380. beatFanSong.addEventListener("play", onPlay);
  381. beatFanSong.addEventListener("ended", onEnded);
  382. beatBanSong && (beatBanSong.muted = true);
  383. beatMingSong && (beatMingSong.muted = true);
  384. }else if(beatBanSong){
  385. beatBanSong.addEventListener("play", onPlay);
  386. beatBanSong.addEventListener("ended", onEnded);
  387. beatMingSong && (beatMingSong.muted = true);
  388. }
  389. if(beatMingSong){
  390. beatMingSong.addEventListener("play", onPlay);
  391. beatMingSong.addEventListener("ended", onEnded);
  392. }
  393. if(beatMingSongGirl){
  394. beatMingSongGirl.addEventListener("play", onPlay);
  395. beatMingSongGirl.addEventListener("ended", onEnded);
  396. }
  397. // 给男声女声赋值
  398. const userGender = storeData.user.gender
  399. // 当不为null 和undefined的时候 取userGender的值
  400. if(userGender!==undefined && userGender!==null){
  401. audioData.mingSongType = userGender
  402. }
  403. // 重新根据性别赋值
  404. changeMingSongType()
  405. state.audioDone = true;
  406. state.isLoading = false
  407. console.timeEnd("音频加载时间")
  408. console.log("音频数据:",audioData)
  409. api_playProgress(progress);
  410. } else {
  411. state.audioDone = true;
  412. state.isLoading = false
  413. const songEndTime = state.times[state.times.length - 1 || 0]?.endtime || 0
  414. audioData.duration = songEndTime
  415. // 监听midi播放进度
  416. api_cloudTimeUpdae(midiProgress);
  417. // 监听midi播放结束
  418. api_cloudplayed(midiPlayEnd);
  419. }
  420. });
  421. onUnmounted(() => {
  422. api_remove_cloudplayed(midiPlayEnd);
  423. api_remove_cloudTimeUpdae(midiProgress);
  424. });
  425. // console.log(state.playMode, state.midiUrl);
  426. return () => (
  427. <>
  428. <div class={styles.audioList}>
  429. {state.playMode === "MIDI" && state.speed != 0 && (
  430. <iframe
  431. style={{ display: "none" }}
  432. ref={midiRef}
  433. src={`/midi/index.html`}
  434. onLoad={handleLoad}
  435. />
  436. )}
  437. </div>
  438. </>
  439. );
  440. },
  441. });