runtime.ts 30 KB

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