runtime.ts 29 KB

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