use-app.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. import { onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue'
  2. import { useClientType, useOriginSearch } from '.'
  3. import request from '/src/helpers/request'
  4. import originRequest from 'umi-request'
  5. import store from 'store'
  6. import runtime, * as RuntimeUtils from '/src/pages/detail/runtime'
  7. import detailState, { GradualTimes, isRhythmicExercises } from '/src/pages/detail/state'
  8. import SettingState from '/src/pages/detail/setting-state'
  9. import { listenerMessage, postMessage } from '/src/helpers/native-message'
  10. import audiosInstance from '/src/helpers/multiple-audio'
  11. import { formatXML, onlyVisible, getCustomInfo } from '/src/pages/detail/helpers'
  12. import { MusicSheelDetail, ShaeetStatusType } from '../index.d'
  13. import { browser, getRequestHostname, isEncoded, setGlobalData } from '/src/helpers/utils'
  14. import formatId, { formatdata, getSubjectIdCode } from '../fingering/format-id'
  15. import { evaluatStopPlay } from '../buttons/evaluating'
  16. import state from '/src/pages/detail/state'
  17. import { getGradualLengthByXml } from '/src/pages/detail/calcSpeed'
  18. import { musicInfo } from '../state'
  19. import { getInstrumentName } from '../helpers/instruments'
  20. const search = useOriginSearch()
  21. const skpList = ['Ukulele']
  22. /**
  23. * 特殊教材分类id
  24. */
  25. export const classids = [1, 2, 6, 7, 8, 9, 3, 10, 11, 12, 13, 4, 14, 15, 16, 17, 30, 31, 35, 36, 108]; // 大雅金唐, 竖笛教程, 声部训练展开的分类ID
  26. // export const classids = [1, 30, 97]; // [大雅金唐, 竖笛教程, 声部训练]
  27. /**
  28. * 大雅金唐类目
  29. */
  30. const daYaClassids = [1, 2, 6, 7, 8, 9, 3, 10, 11, 12, 13, 4, 14, 15, 16, 17]
  31. /**
  32. * 获取xml并前置格式化
  33. * @param url xml地址
  34. * @param detail 音乐详情
  35. * @returns Ref<string>
  36. */
  37. export const useXml = async (url: string, detail: MusicSheelDetail) => {
  38. const partIndex = Number(search['part-index']) || 0
  39. let score = ref<string>('')
  40. try {
  41. const xml = await originRequest(url)
  42. const parseXmlInfo = getCustomInfo(xml)
  43. if (skpList.includes(parseXmlInfo.code)) {
  44. score.value = xml
  45. } else {
  46. score.value = formatXML(parseXmlInfo.parsedXML, {
  47. title: detail.musicSheetName,
  48. })
  49. // 多种乐器分轨合并显示
  50. if (state.isCombineRender) {
  51. const customNoduleInfo = JSON.parse(localStorage.getItem('customNoduleInfo')) || []
  52. const matchMusic = customNoduleInfo.find((n: any) => n.id === id)
  53. const xjNum = matchMusic ? matchMusic.customNum : 4
  54. setGlobalData('wrapNum', xjNum)
  55. } else {
  56. score.value = onlyVisible(score.value, partIndex)
  57. }
  58. state.partIndex = partIndex
  59. }
  60. state.gradual = getGradualLengthByXml(xml)
  61. } catch (error) {}
  62. return score
  63. }
  64. /**
  65. * 设置音频信息
  66. * @param detail 音乐详情
  67. */
  68. export const useMp3s = async (detail: MusicSheelDetail) => {
  69. const search = useOriginSearch()
  70. const partIndex = ((search['part-index'] as string) || 0) as unknown as number
  71. const activebg = detail.background?.[partIndex]
  72. Object.assign(musicInfo, activebg)
  73. if (musicInfo.musicSvg) {
  74. try {
  75. musicInfo.musicSvg = typeof musicInfo.musicSvg === 'string' && musicInfo.musicSvg ? JSON.parse(musicInfo.musicSvg) : ''
  76. } catch (error) {}
  77. }
  78. // 伴奏
  79. const backgroundSong = isEncoded(detail.metronomeUrl || '') ? detail.metronomeUrl || '' : encodeURI(detail.metronomeUrl || '')
  80. // 原音
  81. const musicSong = isEncoded(activebg?.audioFileUrl|| '') ? activebg?.audioFileUrl || '' : encodeURI(activebg?.audioFileUrl|| '')
  82. console.log('伴奏:',backgroundSong,'原音:',musicSong)
  83. // 兼容未修改之前
  84. runtime.songs = {
  85. background: backgroundSong ? backgroundSong + '?t=background' : '',
  86. music: musicSong ? musicSong + '?t=music' : '',
  87. }
  88. detailState.isAppPlay = detail.audioType === 'MIDI'
  89. // 如果后台有速度
  90. const isSpecialSong= !classids.includes(detail.musicSheetCategoriesId)
  91. if (!isSpecialSong) {
  92. detailState.playSpeed = detail.playSpeed ? parseFloat(String(detail.playSpeed)) : 100;
  93. detailState.baseSpeed = detailState.playSpeed
  94. } else if (detail.playSpeed && detail.playSpeed !== "") {
  95. // detailState.playSpeed = detail.playSpeed ? parseFloat(String(detail.playSpeed)) : 100;
  96. // detailState.baseSpeed = detailState.playSpeed
  97. detailState.temporarySpeed = detail.playSpeed ? parseFloat(String(detail.playSpeed)) : 90;
  98. }
  99. let defaultExtConfigJson = {
  100. skipTick: false,
  101. repeatedBeats: false,
  102. scoreSize: 'middle',
  103. }
  104. let extConfigJson = {}
  105. detailState.activeDetail = {
  106. ...detail,
  107. examSongId: detail.id,
  108. originalSpeed: 90,
  109. isAppPlay: detail.audioType === 'MIDI',
  110. extConfigJson: {
  111. ...defaultExtConfigJson,
  112. },
  113. }
  114. detailState.isPercussion = musicInfo.musicSubject == '1' || isRhythmicExercises();
  115. try {
  116. extConfigJson = JSON.parse(detail?.extConfigJson || '')
  117. } catch (error) {}
  118. detailState.activeDetail.extConfigJson = {
  119. ...detailState.activeDetail.extConfigJson,
  120. ...extConfigJson,
  121. }
  122. const setZoom = detailState.activeDetail.extConfigJson.scoreSize
  123. const zooms = store.get('zooms') || {}
  124. if (setZoom && !zooms['' + detail.id]) {
  125. store.set('zooms', { ...zooms, ['' + detail.id]: setZoom })
  126. SettingState.sett.scoreSize = setZoom
  127. }
  128. detailState.needTick = (detail.audioType === 'MP3' && detail.mp3Type === 'MP3' && detail.musicSheetType != 'CONCERT' ) || detail.audioType === 'MIDI'
  129. detailState.skipTick = detailState.activeDetail.extConfigJson.skipTick
  130. detailState.repeatedBeats = detailState.activeDetail.extConfigJson.repeatedBeats
  131. if (!runtime.songs['music']) {
  132. RuntimeUtils.changeMode('background')
  133. }
  134. // console.log({ ...detailState.activeDetail })
  135. if (!runtime.audiosInstance) {
  136. runtime.audiosInstance = new audiosInstance(Object.values(runtime.songs) as string[])
  137. }
  138. }
  139. /**
  140. * 获取异形屏信息
  141. * @returns {Promise<void>}
  142. */
  143. export const useSpecialShapedScreen = () => {
  144. const heightRef = ref<number>(0)
  145. postMessage(
  146. {
  147. api: 'isSpecialShapedScreen',
  148. },
  149. (evt) => {
  150. const height = evt?.content.notchHeight
  151. detailState.notchHeight =
  152. (browser().ios ? height * 2 : height) || (evt?.content.isSpecialShapedScreen && browser().ios ? 100 : 0)
  153. heightRef.value = detailState.notchHeight
  154. detailState.isSpecialShapedScreen = evt?.content.isSpecialShapedScreen
  155. document.documentElement.style.setProperty('--popup-loading', detailState.notchHeight / 4 + 'px')
  156. }
  157. )
  158. return [heightRef]
  159. }
  160. /**
  161. * 获取当前曲目信息
  162. * @param id 歌曲id
  163. */
  164. export const useDetail = (id: number | string): [Ref<ShaeetStatusType>, Ref<MusicSheelDetail>] => {
  165. const prefix = getRequestHostname()
  166. const status = ref<ShaeetStatusType>('loading')
  167. const data = ref<MusicSheelDetail>({})
  168. status.value = 'loading'
  169. request
  170. .get(`/musicSheet/detail/${id}`, {
  171. prefix: prefix,
  172. })
  173. .then((res) => {
  174. useMp3s(res.data)
  175. data.value = {
  176. ...res.data,
  177. code:
  178. Array.isArray(res?.data?.background) && res.data.background.length
  179. ? getSubjectIdCode(res.data.background[0].musicSubject)
  180. : '',
  181. }
  182. // notation: 是能转简谱 0: 不可以,需要设置成五线谱模式, 是否是切换简谱
  183. if (data.value.notation == 0 || !sessionStorage.getItem('notation')) {
  184. SettingState.sett.type = 'staff'
  185. }
  186. // 设置是否特殊曲谱, 是特殊曲谱取反(不理解之前的思考逻辑), 使用后台设置的速度
  187. detailState.isSpecialBookCategory = !classids.includes(res.data.musicSheetCategoriesId)
  188. // 大雅金唐类目,#9248 优化
  189. detailState.isDaYaCategory = daYaClassids.includes(res.data.musicSheetCategoriesId)
  190. if (detailState.isDaYaCategory) {
  191. ;(window as any).customSectionAmount = SettingState.sett.openCustomNodule
  192. const customNoduleInfo = JSON.parse(localStorage.getItem('customNoduleInfo')) || []
  193. const matchMusic = customNoduleInfo.find((n: any) => n.id === id)
  194. const xjNum = matchMusic ? matchMusic.customNum : 4
  195. setGlobalData('wrapNum', xjNum)
  196. }
  197. detailState.subjectId = Number(musicInfo.musicSubject)
  198. // 打击乐声部下的曲目,需要合并展示所有分轨
  199. if (Number(res.data.musicSubject) === 1 && res.data.background?.length > 1) {
  200. state.isCombineRender = true
  201. // 开启自定义每行显示的小节数
  202. ;(window as any).customSectionAmount = true
  203. setGlobalData('multitrack', res.data.background?.length)
  204. }
  205. ;(window as any).DYSubjectId = formatId(data.value.code as any)
  206. /**
  207. * 长笛教程2-5-2,符杆全部朝下
  208. * DYMusicalOrientation,0:朝上;1:朝下
  209. */
  210. if (id == 904) {
  211. ;(window as any).DYMusicalOrientation = 1
  212. }
  213. if (id == 829) {
  214. ;(window as any).DYMusicalOrientation = 0
  215. }
  216. status.value = 'success'
  217. // 额外配置
  218. let extConfigJson = {
  219. gradualTimes: {},
  220. }
  221. try {
  222. if (data.value?.extConfigJson) {
  223. extConfigJson = {
  224. ...extConfigJson,
  225. ...JSON.parse(data.value.extConfigJson),
  226. }
  227. }
  228. } catch (error) {}
  229. state.gradualTimes = extConfigJson.gradualTimes as GradualTimes
  230. // 合奏设置
  231. if (res?.data?.musicSheetType == 'CONCERT') {
  232. const backgrounds = res?.data?.background || [];
  233. const partIndex = Number(search['part-index']) || 0
  234. let track = backgrounds[partIndex]?.track
  235. if (backgrounds[0]?.track?.toLocaleUpperCase() == 'COMMON'){
  236. track = backgrounds[partIndex + 1]?.track
  237. }
  238. const instrumentName = getInstrumentName(track)
  239. console.log("🚀 ~ track:", track)
  240. detailState.partName = track + (instrumentName ? `(${instrumentName})` : '')
  241. const _track = Object.keys(formatdata).filter((key) => key.includes(track) || track.includes(key))[0]
  242. data.value.code = _track
  243. }
  244. })
  245. .catch(() => (status.value = 'error'))
  246. return [status, data]
  247. }
  248. /**
  249. * 监听后台切换状态,暂停播放与评测
  250. */
  251. export const useSuspendPlay = () => {
  252. listenerMessage('suspendPlay', () => {
  253. if (detailState.activeTick > -1) {
  254. RuntimeUtils.stopTick()
  255. }
  256. console.log(runtime.playState)
  257. if (runtime.playState === 'play') {
  258. RuntimeUtils.resetPlayStatus()
  259. if (runtime.evaluatingStatus) {
  260. // postMessage(
  261. // {
  262. // api: 'pauseRecording',
  263. // },
  264. // () => {
  265. // detailState.isPauseRecording = true
  266. // }
  267. // )
  268. evaluatStopPlay()
  269. }
  270. }
  271. })
  272. }