index.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. import { useToggle } from '@vant/use'
  2. import { Button } from 'vant'
  3. import { defineComponent, nextTick, onMounted, onUnmounted, reactive, ref, Teleport, Transition, watch } from 'vue'
  4. import { useClientType, useOriginSearch } from '../../uses'
  5. import styles from './index.module.less'
  6. import { IPostMessage, listenerMessage, postMessage, removeListenerMessage } from '/src/helpers/native-message'
  7. import request from '/src/helpers/request'
  8. import { getPlatform, getRequestHostname } from '/src/helpers/utils'
  9. import { setStepIndex } from '/src/pages/detail/helpers'
  10. import state, { refreshView, setCurrentTime } from '/src/pages/detail/runtime'
  11. import detailState from '/src/pages/detail/state'
  12. import iconFollwBtn from './icons/icon-follwBtn.png'
  13. import { unitTestData } from '/src/subpages/colexiu/unitTest/index'
  14. // 显示或隐藏播放按钮
  15. const togglePlayer = (show: boolean = false) => {
  16. let globalPlayer: HTMLElement = document.querySelector('#globalPlayer')!
  17. if (globalPlayer) {
  18. globalPlayer.style.display = show ? '' : 'none'
  19. }
  20. }
  21. const data = reactive({
  22. list: [] as any, // 频率列表
  23. index: 0,
  24. start: false,
  25. times: [] as any[], // 音符列表
  26. endIndex: 0, // 如果为选段结束的小节
  27. })
  28. const [isOpen, changeOpen] = useToggle(true)
  29. const noteFrequency = ref(0)
  30. const audioFrequency = ref(0)
  31. const followTime = ref(0)
  32. // 切换录音
  33. const openToggleRecord = (open: boolean = true) => {
  34. postMessage({
  35. api: 'cloudToggleFollow',
  36. content: {
  37. state: open ? 'start' : 'end',
  38. },
  39. })
  40. // 记录跟练时长
  41. if (open) {
  42. followTime.value = Date.now()
  43. } else {
  44. const playTime = Date.now() - followTime.value
  45. if (followTime.value !== 0 && playTime > 0) {
  46. followTime.value = 0
  47. // 已有全局记录时长,不用单独记录了
  48. // updatePlayTime(playTime / 1000)
  49. }
  50. }
  51. }
  52. const initBehaviorId = '' + new Date().valueOf()
  53. /**
  54. * 记录时长
  55. */
  56. async function updatePlayTime(time: number) {
  57. const search = useOriginSearch()
  58. const behaviorId = sessionStorage.getItem('behaviorId') || search.behaviorId || initBehaviorId
  59. const prefix = getRequestHostname()
  60. const seearchid = useOriginSearch().id as string
  61. const id = location.hash.split('?')[0].split('/').pop() || seearchid || ''
  62. try {
  63. const res = await request.post('/musicPracticeRecord/save', {
  64. prefix: prefix,
  65. data: {
  66. musicSheetId: id,
  67. sysMusicScoreId: id,
  68. feature: search.feature,
  69. playTime: time,
  70. deviceType: getPlatform(),
  71. behaviorId,
  72. },
  73. })
  74. } catch (err) {}
  75. }
  76. // 清除音符状态
  77. const onClear = () => {
  78. detailState.times.forEach((item) => {
  79. const note: HTMLElement = document.querySelector(`div[data-vf=vf${item.id}]`)!
  80. if (note) {
  81. note.classList.remove('follow-error')
  82. note.classList.remove('follow-success')
  83. // note.classList.add('follow-error')
  84. }
  85. })
  86. }
  87. /** 获取默认开始的音符 */
  88. const getDefaultIndex = () => {
  89. if (unitTestData.isSelectMeasureMode) {
  90. data.endIndex = detailState.times.findIndex(
  91. (n: any) => n.NoteToGraphicalNoteObjectId == detailState.section[1].NoteToGraphicalNoteObjectId
  92. )
  93. const index = detailState.times.findIndex(
  94. (n: any) => n.NoteToGraphicalNoteObjectId == detailState.section[0].NoteToGraphicalNoteObjectId
  95. )
  96. return index > -1 ? index : 0
  97. }
  98. return 0
  99. }
  100. // 开始
  101. const handleStart = () => {
  102. onClear()
  103. data.start = true
  104. openToggleRecord(true)
  105. data.index = getDefaultIndex()
  106. data.list = []
  107. setStepIndex(state.osmd, data.index)
  108. // state.osmd.cursor.reset()
  109. getNoteIndex()
  110. refreshView()
  111. }
  112. // 结束
  113. const handleEnd = () => {
  114. data.start = false
  115. openToggleRecord(false)
  116. data.index = getDefaultIndex()
  117. setStepIndex(state.osmd, data.index)
  118. // state.osmd.cursor.reset()
  119. getNoteIndex()
  120. }
  121. // 下一个
  122. const next = () => {
  123. if (state.osmd.product) {
  124. state.osmd.cursor.setPosition(detailState.times[data.index].cursorBox)
  125. } else {
  126. state.osmd.cursor.next()
  127. }
  128. refreshView()
  129. }
  130. // 获取当前音符
  131. const getNoteIndex = (): any => {
  132. const item = detailState.times[data.index]
  133. if (!item.frequency) {
  134. data.index = data.index + 1
  135. next()
  136. return getNoteIndex()
  137. }
  138. noteFrequency.value = item.frequency
  139. detailState.fixedKey = item.realKey
  140. return {
  141. // ...item,
  142. id: item.id,
  143. min: item.frequency - (item.frequency - item.noteElement.pitch.prevFrequency) * 0.1,
  144. max: item.frequency + (item.noteElement.pitch.nextFrequency - item.frequency) * 0.1,
  145. }
  146. }
  147. let checking = false
  148. const onFollowTime = (evt?: IPostMessage) => {
  149. if (unitTestData.isSelectMeasureMode && data.index >= data.endIndex) {
  150. handleEnd()
  151. return
  152. }
  153. const frequency: number = evt?.content?.frequency
  154. audioFrequency.value = frequency
  155. data.list.push(frequency)
  156. checked()
  157. }
  158. const checked = () => {
  159. if (checking) return
  160. checking = true
  161. const item = getNoteIndex()
  162. for (let i = 0; i < data.list.length; i++) {
  163. const frequency = data.list[i]
  164. if (frequency > item.min && frequency < item.max) {
  165. console.log(item.min, frequency, item.max)
  166. next()
  167. data.index += 1
  168. data.list = data.list.slice(i + 1)
  169. setColor(item, true)
  170. checking = false
  171. return
  172. }
  173. }
  174. setColor(item)
  175. checking = false
  176. }
  177. const setColor = (item: any, isRight = false) => {
  178. const note: HTMLElement = document.querySelector(`div[data-vf=vf${item.id}]`)!
  179. if (note) {
  180. if (isRight) {
  181. note.classList.remove('follow-error')
  182. note.classList.add('follow-success')
  183. } else {
  184. note.classList.remove('follow-success')
  185. note.classList.add('follow-error')
  186. }
  187. }
  188. }
  189. export default defineComponent({
  190. name: 'follow',
  191. setup(props, { expose }) {
  192. onMounted(() => {
  193. togglePlayer()
  194. listenerMessage('cloudFollowTime', onFollowTime)
  195. })
  196. onUnmounted(() => {
  197. removeListenerMessage('cloudFollowTime', onFollowTime)
  198. togglePlayer(true)
  199. onClear()
  200. setStepIndex(state.osmd, 0)
  201. })
  202. expose({
  203. data,
  204. handleEnd,
  205. })
  206. return () => (
  207. <Teleport to="#colexiu-detail-music-sheet">
  208. <div class={styles.follow}>
  209. <Transition name="start" duration={300}>
  210. {!data.start && (
  211. <Button
  212. style={{
  213. backgroundImage: `url(${iconFollwBtn})`,
  214. marginLeft: detailState.isSpecialShapedScreen ? `${detailState.notchHeight / 4}px` : '',
  215. }}
  216. class={[styles.button, styles.start, styles.followBtn]}
  217. onClick={() => handleStart()}
  218. ></Button>
  219. )}
  220. </Transition>
  221. {/* <div class={styles.title}>
  222. <span>音符频率: {noteFrequency.value.toFixed(2)}</span>
  223. <span style={{ color: 'red', marginLeft: '10px' }}>拾音频率: {audioFrequency.value.toFixed(2)}</span>
  224. <span style={{ marginLeft: '10px' }}>拾音长度: {data.list.length}</span>
  225. </div> */}
  226. </div>
  227. </Teleport>
  228. )
  229. },
  230. })