runtime.ts 28 KB

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