import { defineComponent, nextTick, onMounted, onUnmounted, reactive, watch, ref } from 'vue'; // import WaveSurfer from 'wavesurfer.js'; import styles from './index.module.less'; import MSticky from '@/components/m-sticky'; import MHeader from '@/components/m-header'; import { Button, Cell, Image, List, Popup, Slider, showDialog, showToast, Sticky, TextEllipsis, NoticeBar } from 'vant'; import iconDownload from './images/icon-download.png'; import iconShare from './images/icon-share.png'; import iconDelete from './images/icon-delete.png'; import iconEdit from './images/edit.png'; import iconUpward from './images/upward.png'; import iconMember from './images/icon-member.png'; import iconZan from './images/icon-zan.png'; import promptImg from './images/prompt.png'; import confirmImg from './images/confirm.png'; import canceImg from './images/cance.png'; import iconZanActive from './images/icon-zan-active.png'; import iconPlay from './images/icon-play.png'; import iconPause from './images/icon-pause.png'; import { postMessage, promisefiyPostMessage } from '@/helpers/native-message'; import { browser, getGradeCh, getSecondRPM, vaildMusicScoreUrl } from '@/helpers/utils'; import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router'; import { api_userMusicDetail, api_userMusicRemove, api_userMusicStarPage } from './api'; import MEmpty from '@/components/m-empty'; import dayjs from 'dayjs'; import MVideo from '@/components/m-video'; import ShareModel from './share-model'; import { usePageVisibility, useEventListener } from '@vant/use'; import "plyr/dist/plyr.css"; import Plyr from "plyr"; import { Vue3Lottie } from "vue3-lottie"; import audioBga from "./images/audioBga.json"; import audioBga1 from "./images/leftCloud.json"; import audioBga2 from "./images/rightCloud.json"; import videobg from "./images/videobg.png"; //import playProgressData from "./playCreation/playProgress" import Loading from './loading'; import { generateMixedData } from "./audioVisualDraw" import backImg from "./images/back.png"; export default defineComponent({ name: 'creation-detail', setup() { const {isApp, isTablet} = browser() const route = useRoute(); const router = useRouter(); const isScreenScroll = ref(false) const mStickyBottom = ref() const mStickyUpward = ref() const state = reactive({ id: route.query.id, deleteStatus: false, shareStatus: false, playType: '' as 'Audio' | 'Video' | '', // 播放类型 musicDetail: {} as any, isClick: false, list: [] as any, listState: { dataShow: true, // 判断是否有数据 loading: false, finished: false }, params: { page: 1, rows: 20 }, _plrl: null as any, heightV:0, heightB:0 }); const plyrState = reactive({ duration: 0, currentTime: 0, mediaTimeShow: false, playIngShow: true }) // 谱面 const staffState = reactive({ staffSrc: "", isShow: false, height:"initial", speedRate:1, musicRenderType:"staff", partIndex: 0 }) const isLandscapeScreen = ref(false) const staffDom= ref() const {playStaff, pauseStaff, updateProgressStaff} = staffMoveInstance() // 获取列表 const getStarList = async () => { try { if (state.isClick) return; state.isClick = true; const res = await api_userMusicStarPage({ userMusicId: state.id, ...state.params }); state.listState.loading = false; const result = res.data || {}; // 处理重复请求数据 if (state.list.length > 0 && result.current === 1) { return; } state.list = state.list.concat(result.rows || []); state.listState.finished = result.current >= result.pages; state.params.page = result.current + 1; state.listState.dataShow = state.list.length > 0; state.isClick = false; } catch { state.listState.dataShow = false; state.listState.finished = true; state.isClick = false; } }; // 删除作品 const onDelete = async () => { try { await api_userMusicRemove({ id: state.id }); setTimeout(() => { state.deleteStatus = false; showToast('删除成功'); }, 100); setTimeout(() => { if (isApp) { postMessage({ api: 'goBack' }); } else { router.back(); } }, 1200); } catch { // } }; // 下载 const onDownload = async () => { await promisefiyPostMessage({ api: 'saveFile', content: { url: state.musicDetail.videoUrl } }); }; // 滚动事件 const cleanScrollEvent = useEventListener('scroll', () => { const height = window.scrollY || document.documentElement.scrollTop // 防止多次调用 if(height > 0 && isScreenScroll.value === false){ isScreenScroll.value = true setStatusBarTextColor(false) } if(height <= 0){ isScreenScroll.value = false setStatusBarTextColor(true) } }) // 设置导航栏颜色 function setStatusBarTextColor(isWhite:boolean){ postMessage({ api: 'setStatusBarTextColor', content: { statusBarTextColor: isWhite } }) } const pageVisibility = usePageVisibility(); watch(pageVisibility, value => { if (value === 'hidden') { state._plrl?.pause(); } }); // 初始化 媒体播放 function initMediaPlay(){ const id = state.playType === "Audio" ? "#audioMediaSrc" : "#videoMediaSrc"; state._plrl = new Plyr(id, { controls: ["play", "progress", "current-time", "duration"], fullscreen: { enabled: false, fallback: false } }); const player = state._plrl // 创建音波数据 if(state.playType === "Audio"){ const audioDom = document.querySelector("#audioMediaSrc") as HTMLAudioElement const canvasDom = document.querySelector("#audioVisualizer") as HTMLCanvasElement const { pauseVisualDraw, playVisualDraw } = audioVisualDraw(audioDom, canvasDom) player.on('play', () => { playVisualDraw() }); player.on('pause', () => { pauseVisualDraw() }); } // player.on('loadedmetadata', () => { // player.currentTime = playProgressData.playProgress // }); player.on("timeupdate", ()=>{ plyrState.currentTime = player.currentTime }) player.on('play', () => { plyrState.playIngShow = false playStaff() }); player.on('pause', () => { plyrState.playIngShow = true pauseStaff() }); player.on('ended', () => { player.currentTime = 0 if(!player.playing){ setTimeout(() => { updateProgressStaff(player.currentTime) }, 100); } }); // 处理按压事件 const handleStart = () => { if(isLandscapeScreen.value){ return } plyrState.duration = player.duration plyrState.mediaTimeShow = true }; // 处理松开事件 const handleEnd = () => { plyrState.mediaTimeShow = false // 暂停的时候调用 if(!player.playing){ updateProgressStaff(player.currentTime) } }; const progressDom = document.querySelector("#playMediaSection .plyr__controls .plyr__progress__container") as HTMLElement progressDom.addEventListener('mousedown', handleStart); progressDom.addEventListener('touchstart', handleStart); progressDom.addEventListener('mouseup', handleEnd); progressDom.addEventListener('touchend', handleEnd); } //点击改变播放状态 function handlerClickPlay(event?:MouseEvent){ // 原生 播放暂停按钮 点击的时候 不触发 // @ts-ignore if(event?.target?.matches('button.plyr__control')){ return } if (state._plrl.playing) { state._plrl.pause(); } else { state._plrl.play(); } } /** * 音频可视化 * @param audioDom * @param canvasDom * @param fftSize 2的幂数,最小为32 */ function audioVisualDraw(audioDom: HTMLAudioElement, canvasDom: HTMLCanvasElement, fftSize = 128) { type propsType = { canvWidth: number; canvHeight: number; canvFillColor: string; lineColor: string; lineGap: number } // canvas const canvasCtx = canvasDom.getContext("2d")! let { width, height } = canvasDom.getBoundingClientRect() width = Math.ceil(width) height = Math.ceil(height) canvasDom.width = width canvasDom.height = height // audio // let audioCtx : AudioContext | null = null // let analyser : AnalyserNode | null = null // let source : MediaElementAudioSourceNode | null = null // const dataArray = new Uint8Array(fftSize / 2) const draw = (data: Uint8Array, ctx: CanvasRenderingContext2D, { lineGap, canvWidth, canvHeight, canvFillColor, lineColor }: propsType) => { if (!ctx) return const w = canvWidth const h = canvHeight fillCanvasBackground(ctx, w, h, canvFillColor) // 可视化 const dataLen = data.length let step = (w / 2 - lineGap * dataLen) / dataLen step < 1 && (step = 1) const midX = w / 2 const midY = h / 2 let xLeft = midX for (let i = 0; i < dataLen; i++) { const value = data[i] const percent = value / 255 // 最大值为255 const barHeight = percent * midY canvasCtx.fillStyle = lineColor // 中间加间隙 if (i === 0) { xLeft -= lineGap / 2 } canvasCtx.fillRect(xLeft - step, midY - barHeight, step, barHeight) canvasCtx.fillRect(xLeft - step, midY, step, barHeight) xLeft -= step + lineGap } let xRight = midX for (let i = 0; i < dataLen; i++) { const value = data[i] const percent = value / 255 // 最大值为255 const barHeight = percent * midY canvasCtx.fillStyle = lineColor if (i === 0) { xRight += lineGap / 2 } canvasCtx.fillRect(xRight, midY - barHeight, step, barHeight) canvasCtx.fillRect(xRight, midY, step, barHeight) xRight += step + lineGap } } const fillCanvasBackground = (ctx: CanvasRenderingContext2D, w: number, h: number, colors: string) => { ctx.clearRect(0, 0, w, h) ctx.fillStyle = colors ctx.fillRect(0, 0, w, h) } const requestAnimationFrameFun = () => { // requestAnimationFrame(() => { // //analyser?.getByteFrequencyData(dataArray) // draw(generateMixedData(48), canvasCtx, { // lineGap: 2, // canvWidth: width, // canvHeight: height, // canvFillColor: "transparent", // lineColor: "rgba(255, 255, 255, 0.7)" // }) // if (!isPause) { // requestAnimationFrameFun() // } // }) const _time = setInterval(() => { if (isPause) { clearInterval(_time) return } //analyser?.getByteFrequencyData(dataArray) draw(generateMixedData(48), canvasCtx, { lineGap: 2, canvWidth: width, canvHeight: height, canvFillColor: "transparent", lineColor: "rgba(255, 255, 255, 0.7)" }) }, 300); } let isPause = true const playVisualDraw = () => { // if (!audioCtx) { // audioCtx = new AudioContext() // source = audioCtx.createMediaElementSource(audioDom) // analyser = audioCtx.createAnalyser() // analyser.fftSize = fftSize // source?.connect(analyser) // analyser.connect(audioCtx.destination) // } //audioCtx.resume() // 重新更新状态 加了暂停和恢复音频音质发生了变化 所以这里取消了 isPause = false requestAnimationFrameFun() } const pauseVisualDraw = () => { isPause = true requestAnimationFrame(()=>{ canvasCtx.clearRect(0, 0, width, height); }) //audioCtx?.suspend() // 暂停 加了暂停和恢复音频音质发生了变化 所以这里取消了 // source?.disconnect() // analyser?.disconnect() } return { playVisualDraw, pauseVisualDraw } } function handlerBack(event:any){ event.stopPropagation() verticalScreen() } function landscapeScreen(){ postMessage({ api: "setRequestedOrientation", content: { orientation: 0, }, }); isLandscapeScreen.value = true } function verticalScreen(){ postMessage({ api: "setRequestedOrientation", content: { orientation: 1, }, }); isLandscapeScreen.value = false } function handlerLandscapeScreen(event:any){ event.stopPropagation() if(!isLandscapeScreen.value){ landscapeScreen() } // playProgressData.playState = !!state._plrl?.playing // playProgressData.playProgress = state._plrl?.currentTime || 0 // router.push({ // path:"/playCreation", // query:{ // resourceUrl:encodeURIComponent(state.musicDetail?.videoUrl), // videoBgUrl:encodeURIComponent(state.musicDetail?.videoImg || ""), // musicSheetName:encodeURIComponent(state.musicDetail?.musicSheetName), // username:encodeURIComponent(state.musicDetail?.username), // musicSheetId:encodeURIComponent(state.musicDetail?.musicSheetId), // speedRate:encodeURIComponent(staffState.speedRate), // musicRenderType:encodeURIComponent(staffState.musicRenderType), // partIndex:encodeURIComponent(staffState.partIndex), // } // }) } // 初始化五线谱 function initStaff(){ const src = `${vaildMusicScoreUrl()}/instrument/#/simple-detail?id=${state.musicDetail.musicSheetId}&musicRenderType=${staffState.musicRenderType}&part-index=${staffState.partIndex}`; //const src = `http://192.168.3.122:3000/instrument.html#/simple-detail?id=${state.musicDetail.musicSheetId}&musicRenderType=${staffState.musicRenderType}&part-index=${staffState.partIndex}`; staffState.staffSrc = src window.addEventListener('message', (event) => { const { api, height } = event.data; if (api === 'api_musicPage') { staffState.isShow = true staffState.height = height + "px" // 如果是播放中自动开始播放 不是播放 自动跳转到当前位置 // if(playProgressData.playState){ // handlerClickPlay() // }else{ // updateProgressStaff(state._plrl.currentTime) // } } }); } function staffMoveInstance(){ let isPause = true const requestAnimationFrameFun = () => { requestAnimationFrame(() => { staffDom.value?.contentWindow?.postMessage( { api: 'api_playProgress', content: { currentTime: state._plrl.currentTime * staffState.speedRate } }, "*" ) if (!isPause) { requestAnimationFrameFun() } }) } const playStaff = () => { // 没渲染不执行 if(!staffState.isShow) return isPause = false staffDom.value?.contentWindow?.postMessage( { api: 'api_play' }, "*" ) requestAnimationFrameFun() } const pauseStaff = () => { // 没渲染不执行 if(!staffState.isShow) return isPause = true staffDom.value?.contentWindow?.postMessage( { api: 'api_paused' }, "*" ) } const updateProgressStaff = (currentTime: number) => { // 没渲染不执行 if(!staffState.isShow) return staffDom.value?.contentWindow?.postMessage( { api: 'api_updateProgress', content: { currentTime: currentTime * staffState.speedRate } }, "*" ) } return { playStaff, pauseStaff, updateProgressStaff } } onMounted(async () => { setStatusBarTextColor(true) try { const res = await api_userMusicDetail(state.id); // console.log(res); if (res.code === 999) { showDialog({ message: res.message, theme: 'round-button', confirmButtonColor: 'linear-gradient(73deg, #5BECFF 0%, #259CFE 100%)' }).then(() => { if (isApp) { postMessage({ api: 'goBack' }); } else { router.back(); } }); return; } state.musicDetail = res.data || {}; try{ const jsonConfig = JSON.parse(res.data.jsonConfig) jsonConfig.speedRate && (staffState.speedRate = jsonConfig.speedRate) jsonConfig.musicRenderType && (staffState.musicRenderType = jsonConfig.musicRenderType) jsonConfig["part-index"] && (staffState.partIndex = jsonConfig["part-index"]) }catch{ } // 五线谱 initStaff() getStarList(); // 判断是视频还是音频 if (res.data.videoUrl.lastIndexOf('mp4') !== -1) { state.playType = 'Video'; } else { state.playType = 'Audio'; } nextTick(()=>{ initMediaPlay() }) } catch { // } // 横竖屏之后重新计算位置 // requestAnimationFrame(()=>{ // mStickyUpward.value?.onChnageHeight() // mStickyBottom.value?.onChnageHeight() // }) }); onUnmounted(() => { cleanScrollEvent() state._plrl?.destroy() }); // onBeforeRouteLeave((to, from, next)=>{ // if(to.path !== "/playCreation"){ // playProgressData.playProgress = 0 // playProgressData.playState = false // } // next() // }) return () => (
{ console.log(height, 'height', height) state.heightV = height }} > { setStatusBarTextColor(false) }} />
演奏:{state.musicDetail?.username}
{ isLandscapeScreen.value &&
演奏:{state.musicDetail?.username}
} { state.playType === 'Audio' &&
} { state.playType === 'Video' &&
{state.musicDetail?.username} {state.musicDetail.vipFlag && ( )}
{state.musicDetail.subjectName}{' '} {getGradeCh(state.musicDetail.currentGradeNum - 1)}
{state.musicDetail.likeNum}
点赞记录
{state.listState.dataShow ? ( {state.list.map((item: any, index: number) => ( {{ icon: () => ( ), title: () => (

{item.userName}

{item.subjectName}{' '} {getGradeCh(item.currentGradeNum - 1)}

), value: () => (
{dayjs(item.createTime).format('YYYY-MM-DD HH:mm')}
) }}
))}
) : ( )}
{ !isScreenScroll.value &&
} { console.log(height, 'height', height) state.heightB = height }}>

下载

(state.shareStatus = true)}> 分享

(state.deleteStatus = true)}> 删除

{ router.push({ path: '/creation-edit', query: { id: state.id } }); }} />

确定删除吗?

(state.deleteStatus = false)} />
(state.shareStatus = false)} /> { !staffState.isShow && }
); } });