|
@@ -0,0 +1,319 @@
|
|
|
+import { IconMusic } from "@/assets/icons";
|
|
|
+import { isWeixinBrowser } from "@/controllers/wxController";
|
|
|
+import { useEditor } from "@/modules/editor";
|
|
|
+import { PauseCircleOutlined, PlayCircleOutlined } from "@ant-design/icons-vue";
|
|
|
+import { css } from "@linaria/core";
|
|
|
+import { Button, Slider } from "ant-design-vue";
|
|
|
+import { Howl } from "howler";
|
|
|
+import { nanoid } from "nanoid";
|
|
|
+import {
|
|
|
+ defineComponent,
|
|
|
+ reactive,
|
|
|
+ ref,
|
|
|
+ watch,
|
|
|
+ onUnmounted,
|
|
|
+ onMounted,
|
|
|
+} from "vue";
|
|
|
+import { bool, number } from "vue-types";
|
|
|
+declare const WeixinJSBridge: any;
|
|
|
+export const PageMusic = defineComponent({
|
|
|
+ setup() {
|
|
|
+ const { store, helper, controls } = useEditor();
|
|
|
+ const state = reactive({
|
|
|
+ playStatus: false,
|
|
|
+ duration: 0,
|
|
|
+ currentTime: 0,
|
|
|
+ muted: true,
|
|
|
+ });
|
|
|
+ const rootComp = helper.findRootComp();
|
|
|
+ let audioKey = nanoid();
|
|
|
+ let audioBgm = ref();
|
|
|
+ const initAudioBgm = () => {
|
|
|
+ audioBgm.value = null;
|
|
|
+ audioBgm.value = new Howl({
|
|
|
+ src: [rootComp?.value.music],
|
|
|
+ loop: store.isEditMode ? false : true,
|
|
|
+ preload: true,
|
|
|
+ HTML5: true,
|
|
|
+ });
|
|
|
+ controls.mediaCtrl.setMediasInstance(audioKey, audioBgm.value);
|
|
|
+ audioBgm.value.on("load", () => {
|
|
|
+ state.duration = audioBgm.value.duration();
|
|
|
+ if (!store.isEditMode) {
|
|
|
+ if (isWeixinBrowser()) {
|
|
|
+ WeixinJSBridge.invoke(
|
|
|
+ "getNetworkType",
|
|
|
+ {},
|
|
|
+ () => {
|
|
|
+ playAudio(true);
|
|
|
+ },
|
|
|
+ false
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ playAudio(true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ audioBgm.value.on("loaderror", () => {
|
|
|
+ console.log("音频加载失败");
|
|
|
+ });
|
|
|
+ audioBgm.value.on("playerror", () => {
|
|
|
+ console.log("音频播放失败");
|
|
|
+ });
|
|
|
+ audioBgm.value.on("play", () => {
|
|
|
+ controls.mediaCtrl.pauseOtherMedia(audioKey);
|
|
|
+ if (!state.playStatus) {
|
|
|
+ state.playStatus = true;
|
|
|
+ }
|
|
|
+ playStep();
|
|
|
+ });
|
|
|
+ audioBgm.value.on("pause", () => {
|
|
|
+ audioRest();
|
|
|
+ });
|
|
|
+ audioBgm.value.on("end", () => {
|
|
|
+ if (store.isEditMode) {
|
|
|
+ audioRest();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ setTimeout(() => {
|
|
|
+ checkAutoPlay();
|
|
|
+ }, 500);
|
|
|
+ };
|
|
|
+ const checkAutoPlay = () => {
|
|
|
+ if (!audioBgm.value || store.isEditMode) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let playing = audioBgm.value.playing();
|
|
|
+ if (!playing) {
|
|
|
+ playAudio(true);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ const playStep = () => {
|
|
|
+ if (!audioBgm.value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let playing = audioBgm.value.playing();
|
|
|
+ if (!playing) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let seek = audioBgm.value.seek();
|
|
|
+ state.currentTime = seek;
|
|
|
+ requestAnimationFrame(playStep);
|
|
|
+ };
|
|
|
+
|
|
|
+ const playAudio = async (status: boolean) => {
|
|
|
+ if (!audioBgm.value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (status) {
|
|
|
+ audioBgm.value.play();
|
|
|
+ } else {
|
|
|
+ audioRest();
|
|
|
+ }
|
|
|
+ let playing = audioBgm.value.playing();
|
|
|
+ console.log(playing);
|
|
|
+ if (status && playing) {
|
|
|
+ state.playStatus = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ state.playStatus = false;
|
|
|
+ };
|
|
|
+ watch(
|
|
|
+ () => rootComp?.value.music,
|
|
|
+ () => {
|
|
|
+ audioRest();
|
|
|
+ initAudioBgm();
|
|
|
+ }
|
|
|
+ );
|
|
|
+ onMounted(() => {
|
|
|
+ initAudioBgm();
|
|
|
+ });
|
|
|
+ onUnmounted(() => {
|
|
|
+ audioRest();
|
|
|
+ audioBgm.value = null;
|
|
|
+ controls.mediaCtrl.removeMedia(audioKey);
|
|
|
+ });
|
|
|
+ const seekChange = (v: number) => {
|
|
|
+ state.currentTime = v;
|
|
|
+ audioBgm.value && audioBgm.value.seek(v);
|
|
|
+ };
|
|
|
+ const audioRest = () => {
|
|
|
+ if (!audioBgm.value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let playing = audioBgm.value.playing();
|
|
|
+ if (playing) {
|
|
|
+ audioBgm.value.pause();
|
|
|
+ }
|
|
|
+ state.playStatus = false;
|
|
|
+ state.currentTime = 0;
|
|
|
+ audioBgm.value.seek(0);
|
|
|
+ };
|
|
|
+ return () => {
|
|
|
+ const music = rootComp?.value.music;
|
|
|
+ return (
|
|
|
+ <div class={store.isEditMode ? MusicEditStyle : MusicStyle}>
|
|
|
+ {store.isEditMode ? (
|
|
|
+ <AudioPlayer
|
|
|
+ key={music}
|
|
|
+ playStatus={state.playStatus}
|
|
|
+ onStatus={playAudio}
|
|
|
+ onSeekChange={seekChange}
|
|
|
+ duration={state.duration}
|
|
|
+ currentTime={state.currentTime}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <div
|
|
|
+ class={["music_button", state.playStatus ? "rotating" : ""]}
|
|
|
+ onClick={() => {
|
|
|
+ playAudio(!state.playStatus);
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <IconMusic />
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ };
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+const AudioPlayer = defineComponent({
|
|
|
+ props: {
|
|
|
+ playStatus: bool(),
|
|
|
+ currentTime: number(),
|
|
|
+ duration: number(),
|
|
|
+ },
|
|
|
+ emits: ["status", "seekChange"],
|
|
|
+ setup(props, { emit }) {
|
|
|
+ const audioControl = (playStatus: boolean) => {
|
|
|
+ emit("status", playStatus);
|
|
|
+ };
|
|
|
+ const seekChange = (v: any) => {
|
|
|
+ emit("seekChange", v);
|
|
|
+ };
|
|
|
+ const formatTime = (secs?: number) => {
|
|
|
+ if (!secs) {
|
|
|
+ return "00:00";
|
|
|
+ }
|
|
|
+ secs = Math.round(secs);
|
|
|
+
|
|
|
+ const minutes = Math.floor(secs / 60) || 0;
|
|
|
+ const seconds = secs - minutes * 60 || 0;
|
|
|
+
|
|
|
+ return (
|
|
|
+ String(minutes).padStart(2, "0") +
|
|
|
+ ":" +
|
|
|
+ String(seconds).padStart(2, "0")
|
|
|
+ );
|
|
|
+ };
|
|
|
+ return () => {
|
|
|
+ return (
|
|
|
+ <div class={AudioPlayerStyle}>
|
|
|
+ {!props.playStatus ? (
|
|
|
+ <Button
|
|
|
+ type="link"
|
|
|
+ icon={<PlayCircleOutlined style={{ fontSize: "24px" }} />}
|
|
|
+ onClick={() => audioControl(true)}
|
|
|
+ ></Button>
|
|
|
+ ) : (
|
|
|
+ <Button
|
|
|
+ type="link"
|
|
|
+ icon={<PauseCircleOutlined style={{ fontSize: "24px" }} />}
|
|
|
+ onClick={() => audioControl(false)}
|
|
|
+ ></Button>
|
|
|
+ )}
|
|
|
+ <div class={"flex-1 px-10px"}>
|
|
|
+ <Slider
|
|
|
+ disabled={!props.playStatus}
|
|
|
+ tooltipVisible={false}
|
|
|
+ min={0}
|
|
|
+ max={Math.floor(props.duration || 0)}
|
|
|
+ value={props.currentTime}
|
|
|
+ onChange={seekChange}
|
|
|
+ ></Slider>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ {formatTime(props.currentTime)}/{formatTime(props.duration)}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ };
|
|
|
+ },
|
|
|
+});
|
|
|
+const MusicEditStyle = css`
|
|
|
+ flex: 1;
|
|
|
+`;
|
|
|
+const AudioPlayerStyle = css`
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ /* slider style */
|
|
|
+ .ant-slider-disabled {
|
|
|
+ opacity: 0.7;
|
|
|
+ }
|
|
|
+ .ant-slider-step {
|
|
|
+ background-color: rgba(255, 255, 255, 0.27);
|
|
|
+ }
|
|
|
+ .ant-slider-track {
|
|
|
+ border-radius: 4px;
|
|
|
+ background-color: rgba(255, 255, 255, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .ant-slider:not(.ant-slider-disabled):hover {
|
|
|
+ .ant-slider-handle {
|
|
|
+ background-color: @inf-primary-color;
|
|
|
+ &:not(.ant-tooltip-open) {
|
|
|
+ border-color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .ant-slider {
|
|
|
+ &.ant-slider-disabled {
|
|
|
+ .ant-slider-handle {
|
|
|
+ background-color: #bbb;
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .ant-slider-handle {
|
|
|
+ width: 8px;
|
|
|
+ border-radius: 2px;
|
|
|
+ border-color: #fff;
|
|
|
+ background-color: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ant-slider-handle-click-focused {
|
|
|
+ border-color: #fff;
|
|
|
+ background-color: @inf-primary-color;
|
|
|
+ }
|
|
|
+`;
|
|
|
+const MusicStyle = css`
|
|
|
+ position: absolute;
|
|
|
+ top: 10px;
|
|
|
+ right: 10px;
|
|
|
+ z-index: 999;
|
|
|
+ .music_button {
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ display: inline-flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ background-color: rgba(0, 0, 0, 0.5);
|
|
|
+ font-size: 28px;
|
|
|
+ border-radius: 24px;
|
|
|
+ cursor: pointer;
|
|
|
+ &.rotating {
|
|
|
+ animation: myRotate 5s linear infinite;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ @keyframes myRotate {
|
|
|
+ 0% {
|
|
|
+ transform: rotate(0);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ transform: rotate(360deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+`;
|