runtime.ts 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081
  1. /**
  2. * 播放过程中必要的context,效果与state一致仅用于操作,但是包含方法,好处是可以在任意地方调用,操作播放状态
  3. */
  4. import { Toast } from 'vant'
  5. import { reactive, watchEffect } from 'vue'
  6. import store from 'store'
  7. import { constant, throttle, debounce } from 'lodash'
  8. import {
  9. getActtiveNoteByTimes,
  10. getDuration,
  11. getIndex,
  12. getNoteBySlursStart,
  13. setStepIndex,
  14. getSlursNote,
  15. getVoicePartInfo,
  16. } from './helpers'
  17. import SectionHint from '/src/helpers/section-hint'
  18. import { browser, formatTime, getPlatform, getRequestHostname } from '/src/helpers/utils'
  19. import request from '/src/helpers/request'
  20. // import * as Tone from 'tone'
  21. import router from '/src/router'
  22. import appState from '/src/state'
  23. import detailState from './state'
  24. import SettingState from '/src/pages/detail/setting-state'
  25. // import fixtimeRela from '/src/constant/fixtime'
  26. import TickPlayer, { getTickTime } from '/src/components/tick/player'
  27. // import { tickByNumerator } from '/src/components/tick/constant'
  28. import { postMessage, listenerMessage, promisefiyPostMessage } from '/src/helpers/native-message'
  29. import EventEmitter from 'eventemitter3'
  30. import { useClientType, useOriginSearch } from '/src/subpages/colexiu/uses'
  31. import { evaluatPlayerStop } from '/src/subpages/colexiu/buttons/evaluating'
  32. export const event = new EventEmitter()
  33. const browserInfo = browser()
  34. const initBehaviorId = '' + new Date().valueOf()
  35. const getLinkId = (): string => {
  36. const seearchid = useOriginSearch().id as string
  37. return location.hash.split('?')[0].split('/').pop() || seearchid || ''
  38. }
  39. export const getFixtimeRelaVal = () => {
  40. const route: any = router.currentRoute.value
  41. const linkId = location.hash.split('?')[0].split('/').pop()
  42. return 0 //(fixtimeRela as any)[(route.params.id || linkId)] || 0
  43. }
  44. export const getFixTime = (speed: number) => {
  45. const duration: any = getDuration(state.osmd)
  46. let numerator = duration.numerator || 0
  47. let denominator = duration.denominator || 4
  48. if (detailState.repeatedBeats) {
  49. // 音频制作问题仅2拍不重复
  50. numerator = numerator === 2 ? 4 : numerator
  51. }
  52. // 音频制作问题仅2拍不重复
  53. // numerator = numerator === 2 ? 4 : numerator
  54. const time = !detailState.needTick && !detailState.skipTick ? ((denominator * 60) / speed / denominator) * numerator : 0//5 + getFixtimeRelaVal()
  55. return time
  56. }
  57. let prevIndex: number = 0
  58. export type IPlayState = 'init' | 'play' | 'pause' | 'suspend'
  59. export type IMode = 'background' | 'music'
  60. export type ISonges = {
  61. background?: string
  62. music?: string
  63. }
  64. const state = reactive({
  65. /** 曲目信息 */
  66. songs: {} as ISonges,
  67. /** 播放状态 */
  68. playState: 'init' as IPlayState,
  69. /** 当前播放背景 */
  70. sectionHint: new SectionHint(),
  71. /** 音频播放器实例 */
  72. audiosInstance: null as any,
  73. /** 原声伴奏 */
  74. mode: 'music' as IMode,
  75. /** 是否是第一次播放 */
  76. isFirstPlay: true,
  77. metro: null as any,
  78. metroing: false,
  79. /** 时长 */
  80. duration: '0:00',
  81. durationNum: 0,
  82. /** 当前时间 */
  83. currentTime: '0:00',
  84. currentTimeNum: 0,
  85. loading: false,
  86. /** 速度 */
  87. speed: 90,
  88. browser: browser(),
  89. /** 调速是否可见 */
  90. speedShow: false,
  91. /** 播放进度进度是否可见 */
  92. progressShow: false,
  93. touched: false,
  94. /** opendisplaymusicdisplay 实例 */
  95. osmd: null as any,
  96. /** 节拍器实例 */
  97. tickPlayer: null as any,
  98. /** 评测状态 */
  99. evaluatingStatus: false,
  100. /** 评测提示 */
  101. evaluatingTips: false,
  102. clickTime: 0,
  103. evaluatingFixTime: 0,
  104. /** 摄像头状态 */
  105. cameraStatus: false,
  106. /** 录制状态 */
  107. captureStatus: false,
  108. ticking: false,
  109. /** 第几分轨 */
  110. partIndex: 0,
  111. /** 当前音符index */
  112. activeIndex: 0
  113. })
  114. const syncStepIndex = (i: number) => {
  115. // console.log("🚀 ~ i", i)
  116. if (state.osmd.hidden !== false) {
  117. state.osmd.cursor.show()
  118. }
  119. prevIndex = i
  120. setStepIndex(state.osmd, i)
  121. refreshIndex(detailState.times[i]?.time)
  122. }
  123. watchEffect(() => {
  124. detailState.maskStatus = state.playState === 'play'
  125. })
  126. const syncPlayState = async () => {
  127. if (detailState.activeDetail.isAppPlay) {
  128. const cloudGetMediaStatus = await promisefiyPostMessage({
  129. api: 'cloudGetMediaStatus',
  130. })
  131. const status = cloudGetMediaStatus?.content.status
  132. state.playState = status
  133. } else {
  134. state.playState = state.audiosInstance.getStatus()
  135. }
  136. }
  137. export default state
  138. export const setCurrentTime = (time: number) => {
  139. console.log('setCurrentTime', time)
  140. const fixt = time // - 5
  141. // console.log('set', fixt)
  142. detailState.fixedKey = 0
  143. state.currentTimeNum = fixt
  144. state.currentTime = formatTime(fixt)
  145. if (detailState.activeDetail.isAppPlay) {
  146. promisefiyPostMessage({
  147. api: 'cloudSetCurrentTime',
  148. content: {
  149. currentTime: time * 1000,
  150. songID: detailState.activeDetail.examSongId,
  151. },
  152. })
  153. } else {
  154. state.audiosInstance.setCurrentTime(fixt)
  155. }
  156. refreshView()
  157. syncPlayState()
  158. const index = getIndex(detailState.times, state.currentTimeNum)
  159. syncStepIndex(index)
  160. // changeMode(state.mode)
  161. }
  162. export const onTimeChanged = (num: number) => {
  163. const time = Math.min(num, state.durationNum)
  164. const index = getIndex(detailState.times, state.currentTimeNum)
  165. setCurrentTime(time)
  166. syncStepIndex(index)
  167. // console.log('onTimeChanged', time)
  168. // changeMode(state.mode)
  169. }
  170. /** 获取当前MidiId */
  171. export const getActiveMidiId = () => {
  172. return state.osmd?.sheet?.instruments?.[0]?.subInstruments?.[0]?.midiInstrumentID ?? 0
  173. }
  174. /**
  175. * 修改原音或伴奏
  176. * @param val IMode
  177. */
  178. export const changeMode = async (val: IMode) => {
  179. const cm: IMode = val === 'background' ? 'music' : 'background'
  180. console.log(!state.songs[val], val, cm)
  181. if (detailState.activeDetail.isAppPlay) {
  182. const data = new Map()
  183. for (const name of detailState.partListNames) {
  184. data.set(name, 60)
  185. }
  186. for (const name of getVoicePartInfo().partListNames) {
  187. data.set(name, cm === 'background' ? 100 : 0)
  188. }
  189. promisefiyPostMessage({
  190. api: 'cloudVolume',
  191. content: {
  192. activeMidiId: getActiveMidiId(),
  193. activeMidiVolume: cm === 'background' ? 100 : 0,
  194. parts: Array.from(data.keys()).map((item) => ({
  195. name: item,
  196. volume: data.get(item),
  197. })),
  198. },
  199. })
  200. // state.mode = val
  201. // promisefiyPostMessage({
  202. // api: 'cloudSwitch',
  203. // content: {
  204. // songID: detailState.activeDetail.examSongId,
  205. // parts: cm === 'background' ? [] : getVoicePartInfo().partListNames,
  206. // }
  207. // })
  208. }
  209. // if (!state.songs[val]) {
  210. // return
  211. // }
  212. state.mode = val
  213. state.audiosInstance?.setMute(true, state.songs[cm])
  214. state.audiosInstance?.setMute(false, state.songs[val])
  215. }
  216. export const changeAllMode = () => {
  217. if (detailState.activeDetail?.isAppPlay) {
  218. const data = new Map()
  219. for (const name of detailState.partListNames) {
  220. data.set(name, 1)
  221. }
  222. promisefiyPostMessage({
  223. api: 'cloudVolume',
  224. content: {
  225. activeMidiId: getActiveMidiId(),
  226. activeMidiVolume: 100,
  227. parts: Array.from(data.keys()).map((item) => ({
  228. name: item,
  229. volume: data.get(item),
  230. })),
  231. },
  232. })
  233. } else {
  234. state.mode = 'background'
  235. state.audiosInstance?.setMute(true)
  236. }
  237. }
  238. export const changeSpeed = (speed: number, isSave: boolean = true) => {
  239. // console.log(speed)
  240. // const route: any = router.currentRoute.value
  241. const speeds = store.get('speeds') || {}
  242. if (isSave){
  243. speeds[getLinkId()] = speed
  244. store.set('speeds', speeds)
  245. }
  246. state.speed = speed
  247. if (!detailState.activeDetail) return
  248. state.audiosInstance?.setSpeed(speed / detailState.baseSpeed)
  249. promisefiyPostMessage({
  250. api: 'cloudChangeSpeed',
  251. content: {
  252. speed: speed,
  253. originalSpeed: detailState.activeDetail.originalSpeed,
  254. songID: detailState.activeDetail.examSongId,
  255. },
  256. })
  257. // changeMode(state.mode)
  258. if (state.playState === 'play') {
  259. syncStepIndex(getIndex(detailState.times, state.currentTimeNum))
  260. }
  261. }
  262. let nextLook: boolean = false
  263. let syncTimed: boolean = false
  264. export const resetCursor = () => {
  265. if (state.osmd) {
  266. if(state.osmd.product){
  267. state.osmd.cursor.setPosition({...detailState.times[0].cursorBox})
  268. } else {
  269. state.osmd.cursor.reset()
  270. }
  271. state.osmd.cursor.hide()
  272. detailState.fixedKey = 0
  273. }
  274. }
  275. export const refreshIndexBase = (index: number) => {
  276. if (index < 0) return
  277. const { osmd }: any = state
  278. if (osmd) {
  279. if (detailState.times[index]) {
  280. if (!detailState.sectionStatus) {
  281. state.sectionHint.show()
  282. }
  283. if (detailState.times[index] && detailState.times[index].noteElement) {
  284. state.sectionHint.showForElement(detailState.times[index])
  285. }
  286. if (!osmd.product){
  287. if (osmd.cursor.hidden !== false) {
  288. osmd.cursor.reset()
  289. osmd.cursor.show()
  290. detailState.fixedKey = 0
  291. }
  292. }
  293. if (prevIndex !== index) {
  294. setStepIndex(state.osmd, detailState.times[index].i, prevIndex)
  295. prevIndex = index
  296. }
  297. detailState.fixedKey = detailState.times[index].realKey
  298. detailState.activeNote = detailState.times[index]
  299. }
  300. }
  301. }
  302. export const refreshIndex = (ctime?: number) => {
  303. const { osmd }: any = state
  304. if (osmd && (ctime || state.audiosInstance.audio)) {
  305. const currentTimeNum = ctime || (state.audiosInstance.audio as HTMLAudioElement).currentTime
  306. const index = getIndex(detailState.times, currentTimeNum)
  307. state.activeIndex = index
  308. removeRepateBackground(index)
  309. // console.log(currentTimeNum, index, detailState.times[detailState.times.length - 1]?.endtime)
  310. const lastNote = detailState.times[detailState.times.length - 1]
  311. const endtime = lastNote?.sourceEndTime || lastNote?.endtime
  312. if (currentTimeNum > endtime) {
  313. state.osmd.cursor.hide()
  314. state.sectionHint.destroy()
  315. } else {
  316. if (detailState.times[index]) {
  317. refreshIndexBase(index)
  318. }
  319. }
  320. }
  321. }
  322. /** 重复中移除重复的背景 */
  323. export const removeRepateBackground = (index: number) => {
  324. if (state.evaluatingStatus && index) {
  325. const activeNote = detailState.times[index]
  326. const note = detailState.times[index + 1] || activeNote
  327. const number = note?.noteElement?.sourceMeasure?.measureListIndex
  328. // 0 比较特殊重置等操作会导致误操作所以跳过第0个
  329. if (note && detailState.evaluatings[number] && index > 0) {
  330. detailState.evaluatings = {
  331. ...detailState.evaluatings,
  332. [number]: undefined,
  333. }
  334. }
  335. }
  336. }
  337. /**
  338. * 刷新播放的状态
  339. * @param ctime 当前时间
  340. * @returns
  341. */
  342. export const refreshPlayer = async (ctime?: number) => {
  343. // if (!state.durationNum) {
  344. // loadedmetadata()
  345. // }
  346. // const status: IPlayState = state.audiosInstance.getStatus()
  347. const { osmd }: any = state
  348. if (osmd && (ctime || state.audiosInstance.audio)) {
  349. // state.playState = status
  350. const currentTimeNum = ctime || (state.audiosInstance.audio as HTMLAudioElement).currentTime
  351. // console.log('refreshPlayer', currentTimeNum)
  352. const mintime = 0 //detailState.times[0].time
  353. if (currentTimeNum + 1 < mintime) {
  354. setCurrentTime(mintime)
  355. return
  356. } else {
  357. syncTimed = false
  358. }
  359. const nextTime = () => {
  360. if (detailState.sectionStatus && detailState.section.length === 2) {
  361. // console.log("state.currentTimeNum >= detailState.section[0].time", state.currentTimeNum, detailState.section[0].time)
  362. if (currentTimeNum >= detailState.section[0].time) {
  363. detailState.sectionFlash = false
  364. }
  365. const nextNote = detailState.times[detailState.section[1].i + 1]
  366. const time = nextNote
  367. ? nextNote.halfTone === 0
  368. ? detailState.section[1].endtime
  369. : nextNote.time
  370. : state.durationNum
  371. // console.table({
  372. // currentTimeNum,
  373. // isNext: (browserInfo.xiaomi ? 0.2 : 0.08) >= time,
  374. // time,
  375. // nextNote
  376. // })
  377. return currentTimeNum + (browserInfo.xiaomi ? 0.2 : 0.08) >= time
  378. }
  379. return false
  380. }
  381. const isNext = nextTime()
  382. if (isNext) {
  383. // console.log("isNext", detailState.section[1], detailState.section[1].endtime, currentTimeNum)
  384. state.audiosInstance.setMute(true)
  385. state.osmd.cursor.hide()
  386. // resetPlayStatus()
  387. if (detailState.activeDetail?.isAppPlay) {
  388. pause()
  389. } else {
  390. await state.audiosInstance.pause()
  391. }
  392. // setCurrentTime(detailState.section[0].sourceStartTime || detailState.section[0].time)
  393. setSectionModeCurrentTime()
  394. clearAccelerateRefreshPlayer()
  395. setTimeout(() => setPlayState(), 1000)
  396. state.loading = false
  397. return
  398. }
  399. // 当MIDI进度超过最后一个音符时间触发结束事件
  400. if(detailState.activeDetail?.isAppPlay && state.durationNum + 3 < currentTimeNum) {
  401. if (state.evaluatingStatus) {
  402. pause()
  403. event.emit('ended', new Event('ended'))
  404. } else {
  405. if (SettingState.sett.loop) {
  406. await pause()
  407. await setCurrentTime(0)
  408. await play()
  409. } else {
  410. pause()
  411. }
  412. }
  413. }
  414. }
  415. }
  416. /**
  417. * 重置播放状态
  418. * @param notStop 是否停止播放
  419. */
  420. export const resetPlayStatus = async (notStop?: boolean) => {
  421. try {
  422. prevIndex = 0
  423. state.osmd.cursor.reset()
  424. state.osmd.cursor.hide()
  425. detailState.fixedKey = 0
  426. detailState.sectionFlash = false
  427. if (state.sectionHint) {
  428. state.sectionHint.destroy()
  429. }
  430. if (!notStop) {
  431. if (detailState.activeDetail.isAppPlay) {
  432. await promisefiyPostMessage({
  433. api: 'cloudSuspend',
  434. content: {
  435. songID: detailState.activeDetail.examSongId,
  436. },
  437. })
  438. endCapture()
  439. } else {
  440. console.log('resetPlayStatus调用暂停')
  441. state.audiosInstance.pause()
  442. }
  443. }
  444. syncPlayState()
  445. } catch (error) {
  446. console.log('resetPlayStatus错误', error)
  447. }
  448. }
  449. export const play = async () => {
  450. if (state.isFirstPlay) {
  451. resetPlayStatus()
  452. detailState.fixedKey = 0
  453. }
  454. if (detailState.activeDetail.isAppPlay) {
  455. await syncPlayState()
  456. promisefiyPostMessage({
  457. api: 'cloudSuspend',
  458. content: {
  459. songID: detailState.activeDetail.examSongId,
  460. },
  461. })
  462. startCapture()
  463. } else {
  464. state.playState = state.audiosInstance.getStatus()
  465. clearAccelerateRefreshPlayer()
  466. accelerateRefreshPlayer()
  467. startCapture()
  468. }
  469. }
  470. const setDelayTime = async (time: number) => {
  471. return new Promise((resolve) => {
  472. setTimeout(() => {
  473. resolve(time)
  474. }, time)
  475. })
  476. }
  477. /**
  478. * 暂停播放
  479. */
  480. export const pause = async () => {
  481. if (detailState.sectionStatus) {
  482. state.osmd.cursor.hide()
  483. }
  484. if (detailState.activeDetail.isAppPlay) {
  485. await syncPlayState()
  486. await promisefiyPostMessage({
  487. api: 'cloudSuspend',
  488. })
  489. await setDelayTime(200)
  490. endCapture()
  491. } else {
  492. state.playState = state.audiosInstance.getStatus()
  493. clearAccelerateRefreshPlayer()
  494. state.audiosInstance.pause()
  495. setTimeout(() => {
  496. // 确保播放完毕,延迟200ms
  497. endCapture()
  498. }, 200)
  499. }
  500. }
  501. export const waiting = () => {
  502. state.loading = true
  503. }
  504. export const playing = () => {
  505. state.loading = false
  506. }
  507. export const ended = debounce(async (evt: Event) => {
  508. resetPlayStatus()
  509. detailState.fixedKey = 0
  510. if (!state.evaluatingStatus) {
  511. refreshPlayer(0)
  512. if (SettingState.sett.loop) {
  513. await setPlayState()
  514. }
  515. }
  516. setCurrentTime(0)
  517. event.emit('ended', evt)
  518. }, 300, {
  519. 'leading': true,
  520. 'trailing': false
  521. })
  522. let timer: any = null
  523. let now = 0
  524. let nowTime = 0
  525. let prevTime: number = 0
  526. const accelerateRefreshPlayer = () => {
  527. if (timer || !state.audiosInstance) {
  528. return
  529. }
  530. timer = setInterval(() => {
  531. requestAnimationFrame(() => {
  532. refreshPlayer()
  533. if (state.audiosInstance.getStatus() === 'play') {
  534. refreshIndex()
  535. }
  536. })
  537. }, 16.7)
  538. }
  539. const clearAccelerateRefreshPlayer = () => {
  540. clearInterval(timer)
  541. timer = null
  542. now = 0
  543. }
  544. /**
  545. * 选择段落
  546. */
  547. export const sectionChange = () => {
  548. detailState.sectionStatus = !detailState.sectionStatus
  549. clearAccelerateRefreshPlayer()
  550. resetPlayStatus()
  551. if (!detailState.sectionStatus) {
  552. setCurrentTime(0)
  553. detailState.fixedKey = 0
  554. }
  555. if (detailState.sectionStatus && detailState.section.length != 2) {
  556. resetCursor()
  557. }
  558. }
  559. export const clearSectionStatus = () => {
  560. detailState.section = []
  561. detailState.sectionBoundingBoxs = []
  562. detailState.sectionStatus = false
  563. }
  564. export const getFirsrNoteByMeasureListIndex = (index: number, tie = true) => {
  565. for (const note of detailState.times) {
  566. if (note?.noteElement?.sourceMeasure?.measureListIndex === index) {
  567. let noteTies: any = null
  568. for (const item of note.measures) {
  569. if (getSlursNote(item)) {
  570. noteTies = getSlursNote(item)
  571. }
  572. }
  573. if (noteTies) {
  574. if (noteTies.sourceMeasure?.measureListIndex !== index) {
  575. for (const n of detailState.times) {
  576. if (n.noteElement.NoteToGraphicalNoteObjectId === noteTies.NoteToGraphicalNoteObjectId) {
  577. return n
  578. }
  579. }
  580. }
  581. }
  582. return note
  583. }
  584. }
  585. return null
  586. }
  587. export const setSectionModeCurrentTime = () => {
  588. console.log(detailState.needTick, 'setSectionModeCurrentTime')
  589. if (detailState.needTick) {
  590. setCurrentTime(detailState.section[0].sourceStartTime || detailState.section[0].time)
  591. } else {
  592. const measureListIndex = detailState.section[0].noteElement?.sourceMeasure?.measureListIndex
  593. if (measureListIndex > 0) {
  594. setCurrentTime(getFirsrNoteByMeasureListIndex(measureListIndex - 1).time)
  595. detailState.sectionFlash = true
  596. } else {
  597. setCurrentTime(0)
  598. }
  599. }
  600. }
  601. export const setPlayerView = () => {
  602. console.log(detailState.sectionStatus, 'detailState.sectionStatus')
  603. console.log(detailState.needTick)
  604. if (detailState.sectionStatus) {
  605. syncStepIndex(getIndex(detailState.times, state.currentTimeNum))
  606. if (detailState.section.length === 2) {
  607. // setCurrentTime(detailState.section[0].sourceStartTime || detailState.section[0].time)
  608. setSectionModeCurrentTime()
  609. } else {
  610. detailState.section = []
  611. detailState.sectionBoundingBoxs = []
  612. detailState.sectionStatus = false
  613. Toast.clear()
  614. }
  615. }
  616. }
  617. const cloudToggleState = async () => {
  618. const cloudGetMediaStatus = await promisefiyPostMessage({
  619. api: 'cloudGetMediaStatus',
  620. })
  621. const status = cloudGetMediaStatus?.content.status
  622. if (status === 'init') {
  623. return
  624. }
  625. if (status === 'suspend') {
  626. await promisefiyPostMessage({
  627. api: 'cloudPlay',
  628. content: {
  629. songID: detailState.activeDetail.examSongId,
  630. startTime: state.currentTimeNum * 1000,
  631. originalSpeed: detailState.activeDetail.originalSpeed,
  632. speed: state.speed,
  633. hertz: 440//SettingState.sett.hertz,
  634. },
  635. })
  636. startCapture()
  637. } else {
  638. await promisefiyPostMessage({
  639. api: 'cloudSuspend',
  640. })
  641. endCapture()
  642. }
  643. const cloudGetMediaStatused = await promisefiyPostMessage({
  644. api: 'cloudGetMediaStatus',
  645. })
  646. state.playState = cloudGetMediaStatused?.content.status
  647. console.log(cloudGetMediaStatused, 'cloudGetMediaStatused')
  648. }
  649. export const toggleState = async (delay?: number) => {
  650. if (detailState.activeDetail.isAppPlay) {
  651. await cloudToggleState()
  652. } else {
  653. console.log(detailState.activeDetail)
  654. console.log('delay', delay)
  655. state.isFirstPlay = false
  656. setPlayerView()
  657. await state.audiosInstance.togglePlay(delay)
  658. if (!state.evaluatingStatus) {
  659. changeMode(state.mode)
  660. }
  661. state.playState = state.audiosInstance.getStatus()
  662. }
  663. }
  664. const setActiveKey = (index: number) => {
  665. detailState.activeTick = index
  666. }
  667. const setTickStop = () => {
  668. console.log('节拍器结束', new Date().getTime() - state.clickTime)
  669. detailState.activeTick = -1
  670. detailState.activeTickRepeat = 1
  671. toggleState(getTickTime(state.speed / detailState.baseSpeed))
  672. }
  673. let timeliner: any = -1
  674. export const startIntervalTimeline = (maxTime: number, end: () => void) => {
  675. const nowTimeline = new Date().getTime()
  676. let currenttTime = 0
  677. const throttleIndex = throttle(() => {
  678. if (currenttTime) {
  679. refreshView()
  680. }
  681. }, 1200)
  682. const start = () => {
  683. requestAnimationFrame(() => {
  684. currenttTime = (new Date().getTime() - nowTimeline) / 1000
  685. refreshIndex(currenttTime)
  686. if (maxTime && currenttTime >= maxTime) {
  687. clearIntervalTimeline()
  688. end()
  689. }
  690. throttleIndex()
  691. })
  692. }
  693. start()
  694. timeliner = setInterval(() => {
  695. start()
  696. }, 16.7)
  697. }
  698. export const clearIntervalTimeline = () => {
  699. clearInterval(timeliner)
  700. }
  701. const onTickDestroy = () => {
  702. event.emit('tickDestroy')
  703. }
  704. export const setTick = (stop: () => void, speed?: number) => {
  705. // 节拍时间是固定的无需调整
  706. console.log('ticking')
  707. const mixStop = () => {
  708. stop()
  709. event.emit('tickEnd')
  710. }
  711. if (detailState.needTick) {
  712. let { numerator, denominator } = getDuration(state.osmd)
  713. if (state.osmd.numerator && state.osmd.denominator){
  714. numerator = state.osmd.numerator
  715. denominator = state.osmd.denominator
  716. }
  717. if (detailState.activeDetail.isAppPlay) {
  718. state.ticking = true
  719. postMessage(
  720. {
  721. api: 'cloudMetronome',
  722. content: {
  723. // 少量情况下需要重复
  724. repeat: numerator === 2 ? 2 : 1,
  725. denominator,
  726. numerator,
  727. },
  728. },
  729. (res) => {
  730. state.ticking = false
  731. if (res?.content.status === 'finish') {
  732. mixStop()
  733. } else if (res?.content.status === 'cancel') {
  734. event.emit('tickDestroy')
  735. }
  736. }
  737. )
  738. } else {
  739. const activeTickRepeat = numerator === 2 ? 2 : 1
  740. detailState.activeTickRepeat = activeTickRepeat
  741. console.log('ticking')
  742. state.tickPlayer = new TickPlayer(numerator, (speed || state.speed) / 90)
  743. state.tickPlayer?.start(numerator, (speed || state.speed) / 90, activeTickRepeat)
  744. state.tickPlayer?.event.off('tick', setActiveKey)
  745. state.tickPlayer?.event.off('stop', mixStop)
  746. state.tickPlayer?.event.off('destroy', onTickDestroy)
  747. state.tickPlayer?.event.on('tick', setActiveKey)
  748. state.tickPlayer?.event.on('stop', mixStop)
  749. state.tickPlayer?.event.on('destroy', onTickDestroy)
  750. }
  751. } else {
  752. mixStop()
  753. }
  754. }
  755. export const setPlayState = async () => {
  756. // console.log('click play', detailState.activeTick, state.audiosInstance.getStatus())
  757. if (detailState.activeTick > -1 || state.ticking) {
  758. return
  759. }
  760. await syncPlayState()
  761. console.table({
  762. playState: state.playState,
  763. currentTime: state.currentTimeNum,
  764. })
  765. if (state.playState !== 'pause' && state.playState !== 'suspend') {
  766. await toggleState()
  767. return
  768. }
  769. console.log('设置播放')
  770. setPlayerView()
  771. setTick(setTickStop)
  772. }
  773. export const stopTick = () => {
  774. if (state.tickPlayer) {
  775. state.tickPlayer.destroy()
  776. }
  777. event.emit('stopTick')
  778. detailState.activeTickRepeat = 1
  779. detailState.activeTick = -1
  780. }
  781. export const windowResize = () => {
  782. const index = getIndex(detailState.times, state.currentTimeNum)
  783. setTimeout(() => {
  784. state.sectionHint?.showForElement(detailState.times[index]?.noteElement)
  785. }, 200)
  786. }
  787. export const loadedmetadata = () => {
  788. state.duration = formatTime(state.audiosInstance.duration)
  789. state.durationNum = state.audiosInstance.duration
  790. }
  791. let prevDiff: number = 0
  792. let viewing: boolean = false
  793. export const refreshView = () => {
  794. let cursorEl : any = undefined
  795. let containerEl : any = undefined
  796. if (state?.osmd?.product){
  797. cursorEl = state.osmd.cursor.img
  798. containerEl = document.querySelector('#colexiu-detail-music-sheet')
  799. }
  800. const top = Math.max(parseFloat((cursorEl || state.osmd.cursor.cursorElement).style.top), 0)
  801. if (Math.abs(prevDiff - top) > 10 && !viewing) {
  802. viewing = true
  803. setTimeout(() => {
  804. viewing = false
  805. const scrollElement = containerEl ? containerEl :
  806. appState.clintNmae === 'colexiu'
  807. ? state.osmd.container.parentElement.parentElement
  808. : state.osmd.container.parentElement
  809. scrollElement.scrollTo({
  810. top: top,
  811. left: 0,
  812. behavior: 'smooth',
  813. })
  814. prevDiff = top
  815. }, 100)
  816. }
  817. }
  818. const updatePlayTime = async (time: number) => {
  819. const search = useOriginSearch()
  820. const behaviorId = sessionStorage.getItem('behaviorId') || search.behaviorId || initBehaviorId
  821. const prefix = getRequestHostname()
  822. const clientType = useClientType()
  823. if (!state.evaluatingStatus) {
  824. // 如果是后台不需要统计时长
  825. if (clientType === 'web') return
  826. try {
  827. const res = await request.post('/musicPracticeRecord/save', {
  828. prefix: prefix,
  829. requestType: 'json',
  830. data: {
  831. musicSheetId: getLinkId(),
  832. sysMusicScoreId: getLinkId(),
  833. feature: 'EVALUATION',
  834. playTime: time,
  835. deviceType: getPlatform(),
  836. behaviorId,
  837. },
  838. })
  839. event.emit('updatePlayTimeSuccess', res.data)
  840. } catch (error) {}
  841. }
  842. //课后训练
  843. if (search.lessonTrainingId) {
  844. try {
  845. console.log(prefix)
  846. const res = await request.post('/studentLessonTraining/lessonTrainingRecord', {
  847. prefix: prefix,
  848. requestType: 'json',
  849. data: {
  850. materialType: 'SONG',
  851. record: {
  852. clientType: (clientType as string)?.toLocaleUpperCase(),
  853. feature: search.feature,
  854. deviceType: browserInfo.android ? 'ANDROID' : browserInfo.isApp ? 'IOS' : 'WEB',
  855. behaviorId,
  856. playTime: time,
  857. musicSheetId: getLinkId(),
  858. },
  859. courseScheduleId: search.courseScheduleId,
  860. lessonTrainingId: search.lessonTrainingId,
  861. materialId: search.materialId,
  862. },
  863. })
  864. } catch (error) {}
  865. }
  866. }
  867. export const setAudioInit = () => {
  868. state.audiosInstance.event.on('loadedmetadata', loadedmetadata)
  869. state.audiosInstance.event.on('waiting', waiting)
  870. state.audiosInstance.event.on('playing', playing)
  871. state.audiosInstance.event.on('play', play, false)
  872. state.audiosInstance.event.on('pause', pause, false)
  873. state.audiosInstance.event.on('ended', ended, false)
  874. state.audiosInstance.event.on('updatePlayTime', updatePlayTime, false)
  875. listenerMessage('cloudplayed', async () => {
  876. await syncPlayState()
  877. state.currentTimeNum = 0
  878. state.currentTime = '00:00'
  879. state.audiosInstance.event.emit('ended', new Event('ended'))
  880. })
  881. listenerMessage('cloudTimeUpdae', (res) => {
  882. // console.log('cloudTimeUpdae', res?.content.currentTime)
  883. const time = res?.content.currentTime / 1000
  884. // requestAnimationFrame(async () => {
  885. // const cloudGetMediaStatus = await promisefiyPostMessage({
  886. // api: 'cloudGetMediaStatus',
  887. // content: {
  888. // songID: detailState.activeDetail.examSongId,
  889. // }
  890. // })
  891. // const status = cloudGetMediaStatus?.content.status
  892. if (state.playState === 'play') {
  893. state.currentTimeNum = time
  894. state.currentTime = formatTime(time)
  895. refreshPlayer(time)
  896. refreshIndex(time)
  897. }
  898. refreshView()
  899. // })
  900. })
  901. state.audiosInstance.event.on('timeupdate', () => {
  902. state.currentTimeNum = state.audiosInstance.currentTime
  903. state.currentTime = formatTime(state.audiosInstance.currentTime)
  904. requestAnimationFrame(() => {
  905. if (state.audiosInstance.getStatus() === 'play') {
  906. refreshPlayer()
  907. }
  908. refreshView()
  909. })
  910. })
  911. window.addEventListener('resize', windowResize)
  912. }
  913. export const back = () => {
  914. if (state.browser.isApp) {
  915. postMessage({
  916. api: 'back',
  917. })
  918. } else {
  919. // (this as any).$router.replace({
  920. // path: '/',
  921. // })
  922. }
  923. }
  924. export const setStepView = (activeNote: any, time?: number) => {
  925. prevIndex = Math.max(activeNote.i, 0)
  926. syncStepIndex(activeNote.i)
  927. if (time) {
  928. // console.log(time)
  929. refreshPlayer(time)
  930. }
  931. refreshView()
  932. }
  933. export const noteClick = (evt: MouseEvent) => {
  934. if (state.isFirstPlay) {
  935. Toast('开始播放后才能调整进度')
  936. return
  937. }
  938. let activeNote = getNoteBySlursStart(getActtiveNoteByTimes(evt))
  939. if (activeNote) {
  940. const time = activeNote.sourceStartTime || activeNote.time
  941. setCurrentTime(time)
  942. setStepView(activeNote.i, time)
  943. detailState.fixedKey = activeNote.realKey
  944. detailState.activeNote = activeNote
  945. }
  946. }
  947. let playStartTime: number = 0
  948. export const startCapture = async () => {
  949. if (detailState.activeDetail?.isAppPlay) {
  950. playStartTime = new Date().getTime()
  951. console.log('startCapture', playStartTime)
  952. }
  953. // console.log('startCapture:',SettingState.sett.camera, browserInfo.isApp, !state.captureStatus, state.evaluatingStatus, SettingState.eva.save)
  954. if (
  955. SettingState.sett.camera &&
  956. browserInfo.isApp &&
  957. !state.captureStatus &&
  958. state.evaluatingStatus &&
  959. SettingState.eva.save
  960. ) {
  961. state.captureStatus = true
  962. postMessage({
  963. api: 'startCapture',
  964. }, () => {
  965. postMessage({
  966. api: 'setCaptureMode',
  967. content: {
  968. mode: 'evaluating',
  969. },
  970. })
  971. })
  972. }
  973. }
  974. export const endCapture = async () => {
  975. if (detailState.activeDetail?.isAppPlay) {
  976. const playTime = new Date().getTime() - playStartTime
  977. if (playStartTime !== 0 && playTime > 0) {
  978. state.audiosInstance?.event.emit('updatePlayTime', playTime / 1000)
  979. playStartTime = 0
  980. }
  981. }
  982. if (browserInfo.isApp && state.evaluatingStatus && SettingState.sett.camera && state.captureStatus) {
  983. postMessage(
  984. {
  985. api: 'endCapture',
  986. },
  987. () => {
  988. state.captureStatus = false
  989. }
  990. )
  991. evaluatPlayerStop()
  992. }
  993. }
  994. export const setCaptureMode = async () => {
  995. if (browserInfo.isApp && SettingState.sett.camera) {
  996. postMessage({
  997. api: 'setCaptureMode',
  998. content: {
  999. mode: state.evaluatingStatus ? 'evaluating' : 'practice',
  1000. },
  1001. })
  1002. }
  1003. }