runtime.ts 30 KB

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