index.tsx 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  1. import {
  2. defineComponent,
  3. nextTick,
  4. onMounted,
  5. onUnmounted,
  6. reactive,
  7. watch,
  8. ref
  9. } from 'vue';
  10. // import WaveSurfer from 'wavesurfer.js';
  11. import styles from './index.module.less';
  12. import MSticky from '@/components/m-sticky';
  13. import MHeader from '@/components/m-header';
  14. import {
  15. Button,
  16. Cell,
  17. Image,
  18. List,
  19. Popup,
  20. Slider,
  21. showDialog,
  22. showToast,
  23. Sticky,
  24. TextEllipsis,
  25. NoticeBar
  26. } from 'vant';
  27. import iconDownload from './images/icon-download.png';
  28. import iconShare from './images/icon-share.png';
  29. import iconDelete from './images/icon-delete.png';
  30. import iconEdit from './images/edit.png';
  31. import iconUpward from './images/upward.png';
  32. import iconMember from './images/icon-member.png';
  33. import iconZan from './images/icon-zan.png';
  34. import promptImg from './images/prompt.png';
  35. import confirmImg from './images/confirm.png';
  36. import canceImg from './images/cance.png';
  37. import iconZanActive from './images/icon-zan-active.png';
  38. import iconPlay from './images/icon-play.png';
  39. import iconPause from './images/icon-pause.png';
  40. import { postMessage, promisefiyPostMessage } from '@/helpers/native-message';
  41. import { browser, getGradeCh, getSecondRPM, vaildMusicScoreUrl } from '@/helpers/utils';
  42. import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
  43. import {
  44. api_userMusicDetail,
  45. api_userMusicRemove,
  46. api_userMusicStarPage
  47. } from './api';
  48. import MEmpty from '@/components/m-empty';
  49. import dayjs from 'dayjs';
  50. import MVideo from '@/components/m-video';
  51. import ShareModel from './share-model';
  52. import { usePageVisibility, useEventListener } from '@vant/use';
  53. import "plyr/dist/plyr.css";
  54. import Plyr from "plyr";
  55. import { Vue3Lottie } from "vue3-lottie";
  56. import audioBga from "./images/audioBga.json";
  57. import audioBga1 from "./images/leftCloud.json";
  58. import audioBga2 from "./images/rightCloud.json";
  59. import videobg from "./images/videobg.png";
  60. //import playProgressData from "./playCreation/playProgress"
  61. import Loading from './loading';
  62. import { generateMixedData } from "./audioVisualDraw"
  63. import backImg from "./images/back.png";
  64. export default defineComponent({
  65. name: 'creation-detail',
  66. setup() {
  67. const {isApp, isTablet} = browser()
  68. const route = useRoute();
  69. const router = useRouter();
  70. const isScreenScroll = ref(false)
  71. const mStickyBottom = ref()
  72. const mStickyUpward = ref()
  73. const state = reactive({
  74. id: route.query.id,
  75. deleteStatus: false,
  76. shareStatus: false,
  77. playType: '' as 'Audio' | 'Video' | '', // 播放类型
  78. musicDetail: {} as any,
  79. isClick: false,
  80. list: [] as any,
  81. listState: {
  82. dataShow: true, // 判断是否有数据
  83. loading: false,
  84. finished: false
  85. },
  86. params: {
  87. page: 1,
  88. rows: 20
  89. },
  90. _plrl: null as any,
  91. heightV:0,
  92. heightB:0
  93. });
  94. const plyrState = reactive({
  95. duration: 0,
  96. currentTime: 0,
  97. mediaTimeShow: false,
  98. playIngShow: true
  99. })
  100. // 谱面
  101. const staffState = reactive({
  102. staffSrc: "",
  103. isShow: false,
  104. height:"initial",
  105. speedRate:1,
  106. musicRenderType:"staff",
  107. partIndex: 0
  108. })
  109. const isLandscapeScreen = ref(false)
  110. const staffDom= ref<HTMLIFrameElement>()
  111. const {playStaff, pauseStaff, updateProgressStaff} = staffMoveInstance()
  112. // 获取列表
  113. const getStarList = async () => {
  114. try {
  115. if (state.isClick) return;
  116. state.isClick = true;
  117. const res = await api_userMusicStarPage({
  118. userMusicId: state.id,
  119. ...state.params
  120. });
  121. state.listState.loading = false;
  122. const result = res.data || {};
  123. // 处理重复请求数据
  124. if (state.list.length > 0 && result.current === 1) {
  125. return;
  126. }
  127. state.list = state.list.concat(result.rows || []);
  128. state.listState.finished = result.current >= result.pages;
  129. state.params.page = result.current + 1;
  130. state.listState.dataShow = state.list.length > 0;
  131. state.isClick = false;
  132. } catch {
  133. state.listState.dataShow = false;
  134. state.listState.finished = true;
  135. state.isClick = false;
  136. }
  137. };
  138. // 删除作品
  139. const onDelete = async () => {
  140. try {
  141. await api_userMusicRemove({ id: state.id });
  142. setTimeout(() => {
  143. state.deleteStatus = false;
  144. showToast('删除成功');
  145. }, 100);
  146. setTimeout(() => {
  147. if (isApp) {
  148. postMessage({
  149. api: 'goBack'
  150. });
  151. } else {
  152. router.back();
  153. }
  154. }, 1200);
  155. } catch {
  156. //
  157. }
  158. };
  159. // 下载
  160. const onDownload = async () => {
  161. await promisefiyPostMessage({
  162. api: 'saveFile',
  163. content: {
  164. url: state.musicDetail.videoUrl
  165. }
  166. });
  167. };
  168. // 滚动事件
  169. const cleanScrollEvent = useEventListener('scroll', () => {
  170. const height =
  171. window.scrollY ||
  172. document.documentElement.scrollTop
  173. // 防止多次调用
  174. if(height > 0 && isScreenScroll.value === false){
  175. isScreenScroll.value = true
  176. setStatusBarTextColor(false)
  177. }
  178. if(height <= 0){
  179. isScreenScroll.value = false
  180. setStatusBarTextColor(true)
  181. }
  182. })
  183. // 设置导航栏颜色
  184. function setStatusBarTextColor(isWhite:boolean){
  185. postMessage({
  186. api: 'setStatusBarTextColor',
  187. content: { statusBarTextColor: isWhite }
  188. })
  189. }
  190. const pageVisibility = usePageVisibility();
  191. watch(pageVisibility, value => {
  192. if (value === 'hidden') {
  193. state._plrl?.pause();
  194. }
  195. });
  196. // 初始化 媒体播放
  197. function initMediaPlay(){
  198. const id = state.playType === "Audio" ? "#audioMediaSrc" : "#videoMediaSrc";
  199. state._plrl = new Plyr(id, {
  200. controls: ["play", "progress", "current-time", "duration"],
  201. fullscreen: {
  202. enabled: false,
  203. fallback: false
  204. }
  205. });
  206. const player = state._plrl
  207. // 创建音波数据
  208. if(state.playType === "Audio"){
  209. const audioDom = document.querySelector("#audioMediaSrc") as HTMLAudioElement
  210. const canvasDom = document.querySelector("#audioVisualizer") as HTMLCanvasElement
  211. const { pauseVisualDraw, playVisualDraw } = audioVisualDraw(audioDom, canvasDom)
  212. player.on('play', () => {
  213. playVisualDraw()
  214. });
  215. player.on('pause', () => {
  216. pauseVisualDraw()
  217. });
  218. }
  219. // player.on('loadedmetadata', () => {
  220. // player.currentTime = playProgressData.playProgress
  221. // });
  222. player.on("timeupdate", ()=>{
  223. plyrState.currentTime = player.currentTime
  224. })
  225. player.on('play', () => {
  226. plyrState.playIngShow = false
  227. playStaff()
  228. });
  229. player.on('pause', () => {
  230. plyrState.playIngShow = true
  231. pauseStaff()
  232. });
  233. player.on('ended', () => {
  234. player.currentTime = 0
  235. if(!player.playing){
  236. setTimeout(() => {
  237. updateProgressStaff(player.currentTime)
  238. }, 100);
  239. }
  240. });
  241. // 处理按压事件
  242. const handleStart = () => {
  243. if(isLandscapeScreen.value){
  244. return
  245. }
  246. plyrState.duration = player.duration
  247. plyrState.mediaTimeShow = true
  248. };
  249. // 处理松开事件
  250. const handleEnd = () => {
  251. plyrState.mediaTimeShow = false
  252. // 暂停的时候调用
  253. if(!player.playing){
  254. updateProgressStaff(player.currentTime)
  255. }
  256. };
  257. const progressDom = document.querySelector("#playMediaSection .plyr__controls .plyr__progress__container") as HTMLElement
  258. progressDom.addEventListener('mousedown', handleStart);
  259. progressDom.addEventListener('touchstart', handleStart);
  260. progressDom.addEventListener('mouseup', handleEnd);
  261. progressDom.addEventListener('touchend', handleEnd);
  262. }
  263. //点击改变播放状态
  264. function handlerClickPlay(event?:MouseEvent){
  265. // 原生 播放暂停按钮 点击的时候 不触发
  266. // @ts-ignore
  267. if(event?.target?.matches('button.plyr__control')){
  268. return
  269. }
  270. if (state._plrl.playing) {
  271. state._plrl.pause();
  272. } else {
  273. state._plrl.play();
  274. }
  275. }
  276. /**
  277. * 音频可视化
  278. * @param audioDom
  279. * @param canvasDom
  280. * @param fftSize 2的幂数,最小为32
  281. */
  282. function audioVisualDraw(audioDom: HTMLAudioElement, canvasDom: HTMLCanvasElement, fftSize = 128) {
  283. type propsType = { canvWidth: number; canvHeight: number; canvFillColor: string; lineColor: string; lineGap: number }
  284. // canvas
  285. const canvasCtx = canvasDom.getContext("2d")!
  286. let { width, height } = canvasDom.getBoundingClientRect()
  287. width = Math.ceil(width)
  288. height = Math.ceil(height)
  289. canvasDom.width = width
  290. canvasDom.height = height
  291. // audio
  292. // let audioCtx : AudioContext | null = null
  293. // let analyser : AnalyserNode | null = null
  294. // let source : MediaElementAudioSourceNode | null = null
  295. // const dataArray = new Uint8Array(fftSize / 2)
  296. const draw = (data: Uint8Array, ctx: CanvasRenderingContext2D, { lineGap, canvWidth, canvHeight, canvFillColor, lineColor }: propsType) => {
  297. if (!ctx) return
  298. const w = canvWidth
  299. const h = canvHeight
  300. fillCanvasBackground(ctx, w, h, canvFillColor)
  301. // 可视化
  302. const dataLen = data.length
  303. let step = (w / 2 - lineGap * dataLen) / dataLen
  304. step < 1 && (step = 1)
  305. const midX = w / 2
  306. const midY = h / 2
  307. let xLeft = midX
  308. for (let i = 0; i < dataLen; i++) {
  309. const value = data[i]
  310. const percent = value / 255 // 最大值为255
  311. const barHeight = percent * midY
  312. canvasCtx.fillStyle = lineColor
  313. // 中间加间隙
  314. if (i === 0) {
  315. xLeft -= lineGap / 2
  316. }
  317. canvasCtx.fillRect(xLeft - step, midY - barHeight, step, barHeight)
  318. canvasCtx.fillRect(xLeft - step, midY, step, barHeight)
  319. xLeft -= step + lineGap
  320. }
  321. let xRight = midX
  322. for (let i = 0; i < dataLen; i++) {
  323. const value = data[i]
  324. const percent = value / 255 // 最大值为255
  325. const barHeight = percent * midY
  326. canvasCtx.fillStyle = lineColor
  327. if (i === 0) {
  328. xRight += lineGap / 2
  329. }
  330. canvasCtx.fillRect(xRight, midY - barHeight, step, barHeight)
  331. canvasCtx.fillRect(xRight, midY, step, barHeight)
  332. xRight += step + lineGap
  333. }
  334. }
  335. const fillCanvasBackground = (ctx: CanvasRenderingContext2D, w: number, h: number, colors: string) => {
  336. ctx.clearRect(0, 0, w, h)
  337. ctx.fillStyle = colors
  338. ctx.fillRect(0, 0, w, h)
  339. }
  340. const requestAnimationFrameFun = () => {
  341. // requestAnimationFrame(() => {
  342. // //analyser?.getByteFrequencyData(dataArray)
  343. // draw(generateMixedData(48), canvasCtx, {
  344. // lineGap: 2,
  345. // canvWidth: width,
  346. // canvHeight: height,
  347. // canvFillColor: "transparent",
  348. // lineColor: "rgba(255, 255, 255, 0.7)"
  349. // })
  350. // if (!isPause) {
  351. // requestAnimationFrameFun()
  352. // }
  353. // })
  354. const _time = setInterval(() => {
  355. if (isPause) {
  356. clearInterval(_time)
  357. return
  358. }
  359. //analyser?.getByteFrequencyData(dataArray)
  360. draw(generateMixedData(48), canvasCtx, {
  361. lineGap: 2,
  362. canvWidth: width,
  363. canvHeight: height,
  364. canvFillColor: "transparent",
  365. lineColor: "rgba(255, 255, 255, 0.7)"
  366. })
  367. }, 300);
  368. }
  369. let isPause = true
  370. const playVisualDraw = () => {
  371. // if (!audioCtx) {
  372. // audioCtx = new AudioContext()
  373. // source = audioCtx.createMediaElementSource(audioDom)
  374. // analyser = audioCtx.createAnalyser()
  375. // analyser.fftSize = fftSize
  376. // source?.connect(analyser)
  377. // analyser.connect(audioCtx.destination)
  378. // }
  379. //audioCtx.resume() // 重新更新状态 加了暂停和恢复音频音质发生了变化 所以这里取消了
  380. isPause = false
  381. requestAnimationFrameFun()
  382. }
  383. const pauseVisualDraw = () => {
  384. isPause = true
  385. requestAnimationFrame(()=>{
  386. canvasCtx.clearRect(0, 0, width, height);
  387. })
  388. //audioCtx?.suspend() // 暂停 加了暂停和恢复音频音质发生了变化 所以这里取消了
  389. // source?.disconnect()
  390. // analyser?.disconnect()
  391. }
  392. return {
  393. playVisualDraw,
  394. pauseVisualDraw
  395. }
  396. }
  397. function handlerBack(event:any){
  398. event.stopPropagation()
  399. verticalScreen()
  400. }
  401. function landscapeScreen(){
  402. postMessage({
  403. api: "setRequestedOrientation",
  404. content: {
  405. orientation: 0,
  406. },
  407. });
  408. isLandscapeScreen.value = true
  409. }
  410. function verticalScreen(){
  411. postMessage({
  412. api: "setRequestedOrientation",
  413. content: {
  414. orientation: 1,
  415. },
  416. });
  417. isLandscapeScreen.value = false
  418. }
  419. function handlerLandscapeScreen(event:any){
  420. event.stopPropagation()
  421. if(!isLandscapeScreen.value){
  422. landscapeScreen()
  423. }
  424. // playProgressData.playState = !!state._plrl?.playing
  425. // playProgressData.playProgress = state._plrl?.currentTime || 0
  426. // router.push({
  427. // path:"/playCreation",
  428. // query:{
  429. // resourceUrl:encodeURIComponent(state.musicDetail?.videoUrl),
  430. // videoBgUrl:encodeURIComponent(state.musicDetail?.videoImg || ""),
  431. // musicSheetName:encodeURIComponent(state.musicDetail?.musicSheetName),
  432. // username:encodeURIComponent(state.musicDetail?.username),
  433. // musicSheetId:encodeURIComponent(state.musicDetail?.musicSheetId),
  434. // speedRate:encodeURIComponent(staffState.speedRate),
  435. // musicRenderType:encodeURIComponent(staffState.musicRenderType),
  436. // partIndex:encodeURIComponent(staffState.partIndex),
  437. // }
  438. // })
  439. }
  440. // 初始化五线谱
  441. function initStaff(){
  442. const src = `${vaildMusicScoreUrl()}/instrument/#/simple-detail?id=${state.musicDetail.musicSheetId}&musicRenderType=${staffState.musicRenderType}&part-index=${staffState.partIndex}`;
  443. //const src = `http://192.168.3.122:3000/instrument.html#/simple-detail?id=${state.musicDetail.musicSheetId}&musicRenderType=${staffState.musicRenderType}&part-index=${staffState.partIndex}`;
  444. staffState.staffSrc = src
  445. window.addEventListener('message', (event) => {
  446. const { api, height } = event.data;
  447. if (api === 'api_musicPage') {
  448. staffState.isShow = true
  449. staffState.height = height + "px"
  450. // 如果是播放中自动开始播放 不是播放 自动跳转到当前位置
  451. // if(playProgressData.playState){
  452. // handlerClickPlay()
  453. // }else{
  454. // updateProgressStaff(state._plrl.currentTime)
  455. // }
  456. }
  457. });
  458. }
  459. function staffMoveInstance(){
  460. let isPause = true
  461. const requestAnimationFrameFun = () => {
  462. requestAnimationFrame(() => {
  463. staffDom.value?.contentWindow?.postMessage(
  464. {
  465. api: 'api_playProgress',
  466. content: {
  467. currentTime: state._plrl.currentTime * staffState.speedRate
  468. }
  469. },
  470. "*"
  471. )
  472. if (!isPause) {
  473. requestAnimationFrameFun()
  474. }
  475. })
  476. }
  477. const playStaff = () => {
  478. // 没渲染不执行
  479. if(!staffState.isShow) return
  480. isPause = false
  481. staffDom.value?.contentWindow?.postMessage(
  482. {
  483. api: 'api_play'
  484. },
  485. "*"
  486. )
  487. requestAnimationFrameFun()
  488. }
  489. const pauseStaff = () => {
  490. // 没渲染不执行
  491. if(!staffState.isShow) return
  492. isPause = true
  493. staffDom.value?.contentWindow?.postMessage(
  494. {
  495. api: 'api_paused'
  496. },
  497. "*"
  498. )
  499. }
  500. const updateProgressStaff = (currentTime: number) => {
  501. // 没渲染不执行
  502. if(!staffState.isShow) return
  503. staffDom.value?.contentWindow?.postMessage(
  504. {
  505. api: 'api_updateProgress',
  506. content: {
  507. currentTime: currentTime * staffState.speedRate
  508. }
  509. },
  510. "*"
  511. )
  512. }
  513. return {
  514. playStaff,
  515. pauseStaff,
  516. updateProgressStaff
  517. }
  518. }
  519. onMounted(async () => {
  520. setStatusBarTextColor(true)
  521. try {
  522. const res = await api_userMusicDetail(state.id);
  523. // console.log(res);
  524. if (res.code === 999) {
  525. showDialog({
  526. message: res.message,
  527. theme: 'round-button',
  528. confirmButtonColor:
  529. 'linear-gradient(73deg, #5BECFF 0%, #259CFE 100%)'
  530. }).then(() => {
  531. if (isApp) {
  532. postMessage({
  533. api: 'goBack'
  534. });
  535. } else {
  536. router.back();
  537. }
  538. });
  539. return;
  540. }
  541. state.musicDetail = res.data || {};
  542. try{
  543. const jsonConfig = JSON.parse(res.data.jsonConfig)
  544. jsonConfig.speedRate && (staffState.speedRate = jsonConfig.speedRate)
  545. jsonConfig.musicRenderType && (staffState.musicRenderType = jsonConfig.musicRenderType)
  546. jsonConfig["part-index"] && (staffState.partIndex = jsonConfig["part-index"])
  547. }catch{
  548. }
  549. // 五线谱
  550. initStaff()
  551. getStarList();
  552. // 判断是视频还是音频
  553. if (res.data.videoUrl.lastIndexOf('mp4') !== -1) {
  554. state.playType = 'Video';
  555. } else {
  556. state.playType = 'Audio';
  557. }
  558. nextTick(()=>{
  559. initMediaPlay()
  560. })
  561. } catch {
  562. //
  563. }
  564. // 横竖屏之后重新计算位置
  565. // requestAnimationFrame(()=>{
  566. // mStickyUpward.value?.onChnageHeight()
  567. // mStickyBottom.value?.onChnageHeight()
  568. // })
  569. });
  570. onUnmounted(() => {
  571. cleanScrollEvent()
  572. state._plrl?.destroy()
  573. });
  574. // onBeforeRouteLeave((to, from, next)=>{
  575. // if(to.path !== "/playCreation"){
  576. // playProgressData.playProgress = 0
  577. // playProgressData.playState = false
  578. // }
  579. // next()
  580. // })
  581. return () => (
  582. <div
  583. style={
  584. {
  585. '--barheight':state.heightV + "px"
  586. }
  587. }
  588. class={[
  589. styles.creation,
  590. isTablet && styles.creationTablet,
  591. isScreenScroll.value && styles.isScreenScroll
  592. ]}>
  593. <div class={styles.creationBg}></div>
  594. <MSticky position="top"
  595. onBarHeight={(height: any) => {
  596. console.log(height, 'height', height)
  597. state.heightV = height
  598. }}
  599. >
  600. <MHeader
  601. color={isScreenScroll.value ? "#333333" : "#ffffff"}
  602. background={isScreenScroll.value ? `rgb(255,255,255` : "transparent"}
  603. title={state.musicDetail?.musicSheetName}
  604. border={false}
  605. isBack={route.query.platformType != 'ANALYSIS'}
  606. onLeftClick={()=>{ setStatusBarTextColor(false) }}
  607. />
  608. </MSticky>
  609. <div class={styles.singer}>
  610. 演奏:{state.musicDetail?.username}
  611. </div>
  612. <Sticky zIndex={1000} offsetTop={state.heightV - 1 + "px"}>
  613. <div class={[styles.playSection, plyrState.mediaTimeShow && styles.mediaTimeShow, isLandscapeScreen.value&&styles.isLandscapeScreen]} id="playMediaSection" onClick={handlerClickPlay}>
  614. {
  615. isLandscapeScreen.value &&
  616. <div class={styles.backBox}>
  617. <img class={styles.backImg} src={backImg} onClick={handlerBack}/>
  618. <div class={styles.musicDetail}>
  619. <div class={styles.musicSheetName}>
  620. <NoticeBar
  621. text={state.musicDetail?.musicSheetName}
  622. background="none"
  623. />
  624. </div>
  625. <div class={styles.username}>演奏:{state.musicDetail?.username}</div>
  626. </div>
  627. </div>
  628. }
  629. {
  630. state.playType === 'Audio' &&
  631. <div class={styles.audioBox}>
  632. <canvas class={styles.audioVisualizer} id="audioVisualizer"></canvas>
  633. <Vue3Lottie class={styles.audioBga} animationData={audioBga} autoPlay={true} loop={true}></Vue3Lottie>
  634. <Vue3Lottie class={styles.audioBga1} animationData={audioBga1} autoPlay={true} loop={true}></Vue3Lottie>
  635. <Vue3Lottie class={styles.audioBga2} animationData={audioBga2} autoPlay={true} loop={true}></Vue3Lottie>
  636. <audio
  637. crossorigin="anonymous"
  638. id="audioMediaSrc"
  639. src={state.musicDetail?.videoUrl}
  640. controls="false"
  641. preload="metadata"
  642. playsinline
  643. webkit-playsinline
  644. />
  645. </div>
  646. }
  647. {
  648. state.playType === 'Video' &&
  649. <video
  650. id="videoMediaSrc"
  651. class={styles.videoBox}
  652. src={state.musicDetail?.videoUrl}
  653. data-poster={ state.musicDetail?.videoImg || videobg}
  654. poster={ state.musicDetail?.videoImg || videobg}
  655. preload="metadata"
  656. playsinline
  657. webkit-playsinline
  658. x5-playsinline
  659. />
  660. }
  661. <div class={[styles.playLarge, !plyrState.mediaTimeShow && plyrState.playIngShow && styles.playIngShow]}></div>
  662. <div class={styles.mediaTimeCon}>
  663. <div class={styles.mediaTime}>
  664. <div>
  665. {getSecondRPM(plyrState.currentTime)}
  666. </div>
  667. <div class={styles.note}>/</div>
  668. <div class={styles.duration}>
  669. {getSecondRPM(plyrState.duration)}
  670. </div>
  671. </div>
  672. </div>
  673. <div class={styles.landscapeScreen} onClick={handlerLandscapeScreen}></div>
  674. {/* 谱面 */}
  675. {
  676. staffState.staffSrc &&
  677. <div class={[styles.staffBoxCon, staffState.isShow && styles.staffBoxShow]}>
  678. <div
  679. class={styles.staffBox}
  680. style={
  681. {
  682. '--staffBoxHeight':staffState.height
  683. }
  684. }
  685. >
  686. <div class={styles.mask}></div>
  687. <iframe
  688. ref={staffDom}
  689. class={styles.staff}
  690. frameborder="0"
  691. src={staffState.staffSrc}>
  692. </iframe>
  693. </div>
  694. </div>
  695. }
  696. </div>
  697. </Sticky>
  698. <div class={styles.musicSection}>
  699. <div class={styles.avatarInfoBox}>
  700. <div class={styles.avatar}>
  701. <Image class={styles.userLogo} src={state.musicDetail.avatar} />
  702. <div class={styles.infoCon}>
  703. <div class={styles.info}>
  704. <span class={styles.userName}>{state.musicDetail?.username}</span>
  705. {state.musicDetail.vipFlag && (
  706. <img src={iconMember} class={styles.iconMember} />
  707. )}
  708. </div>
  709. <div class={styles.sub}>
  710. {state.musicDetail.subjectName}{' '}
  711. {getGradeCh(state.musicDetail.currentGradeNum - 1)}
  712. </div>
  713. </div>
  714. </div>
  715. <div class={styles.linkes}>
  716. <img src={iconZan} class={styles.iconZan} />
  717. <span>{state.musicDetail.likeNum}</span>
  718. </div>
  719. </div>
  720. <TextEllipsis class={styles.textEllipsis} rows={2} content={state.musicDetail?.desc} expand-text="展开" collapse-text="收起" />
  721. </div>
  722. <div class={styles.likeSection}>
  723. <div class={styles.likeTitle}>点赞记录</div>
  724. {state.listState.dataShow ? (
  725. <List
  726. finished={state.listState.finished}
  727. finishedText=" "
  728. onLoad={getStarList}
  729. immediateCheck={false}>
  730. {state.list.map((item: any, index: number) => (
  731. <Cell
  732. class={[styles.likeItem, index===state.list.length-1&&styles.likeItemLast]}
  733. border={false}
  734. >
  735. {{
  736. icon: () => (
  737. <Image src={item.userAvatar} class={styles.userLogo} />
  738. ),
  739. title: () => (
  740. <div class={styles.userInfo}>
  741. <p class={styles.name}>{item.userName}</p>
  742. <p class={styles.sub}>
  743. {item.subjectName}{' '}
  744. {getGradeCh(item.currentGradeNum - 1)}
  745. </p>
  746. </div>
  747. ),
  748. value: () => (
  749. <div class={styles.time}>
  750. {dayjs(item.createTime).format('YYYY-MM-DD HH:mm')}
  751. </div>
  752. )
  753. }}
  754. </Cell>
  755. ))}
  756. </List>
  757. ) : (
  758. <MEmpty class={styles.mEmpty} image={"empty2"} description="暂无点赞记录" />
  759. )}
  760. </div>
  761. {
  762. !isScreenScroll.value &&
  763. <MSticky ref={mStickyUpward} position="bottom" offsetBottom={state.heightB - 1 + "px"} >
  764. <div class={styles.upward}>
  765. <img src={iconUpward} />
  766. </div>
  767. </MSticky>
  768. }
  769. <MSticky ref={mStickyBottom} position="bottom" onBarHeight={(height: any) => {
  770. console.log(height, 'height', height)
  771. state.heightB = height
  772. }}>
  773. <div class={styles.bottomSection}>
  774. <div class={styles.bottomShare}>
  775. <p onClick={onDownload}>
  776. <img src={iconDownload} />
  777. <span>下载</span>
  778. </p>
  779. <p onClick={() => (state.shareStatus = true)}>
  780. <img src={iconShare} />
  781. <span>分享</span>
  782. </p>
  783. <p onClick={() => (state.deleteStatus = true)}>
  784. <img src={iconDelete} />
  785. <span>删除</span>
  786. </p>
  787. </div>
  788. <img src={iconEdit}
  789. class={styles.btnEdit}
  790. onClick={() => {
  791. router.push({
  792. path: '/creation-edit',
  793. query: {
  794. id: state.id
  795. }
  796. });
  797. }}
  798. />
  799. </div>
  800. </MSticky>
  801. <Popup
  802. v-model:show={state.deleteStatus}
  803. overlay-style={
  804. {
  805. backgroundColor:"rgba(0,0,0,.5)"
  806. }
  807. }
  808. round
  809. class={styles.popupContainer}>
  810. <img class={styles.prompt} src={promptImg} />
  811. <div class={styles.deleteBox}>
  812. <p class={styles.popupContent}>确定删除吗?</p>
  813. <div class={styles.popupBtnGroup}>
  814. <img src={canceImg} onClick={() => (state.deleteStatus = false)} />
  815. <img src={confirmImg} onClick={onDelete} />
  816. </div>
  817. </div>
  818. </Popup>
  819. <Popup
  820. position="bottom"
  821. v-model:show={state.shareStatus}
  822. style={{ background: 'transparent' }}>
  823. <ShareModel
  824. playType={state.playType}
  825. musicDetail={state.musicDetail}
  826. onClose={() => (state.shareStatus = false)}
  827. />
  828. </Popup>
  829. {
  830. !staffState.isShow && <Loading></Loading>
  831. }
  832. </div>
  833. );
  834. }
  835. });