runtime.ts 34 KB

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