index.tsx 17 KB

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