index.tsx 17 KB

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