|
@@ -0,0 +1,222 @@
|
|
|
|
+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 { defineComponent, reactive, ref, watch } from "vue";
|
|
|
|
+import { number, bool } from "vue-types";
|
|
|
|
+import { IconMusic } from "@/assets/icons";
|
|
|
|
+export const PageMusic = defineComponent({
|
|
|
|
+ setup(props) {
|
|
|
|
+ const { store, actions, helper } = useEditor();
|
|
|
|
+ const state = reactive({
|
|
|
|
+ playStatus: false,
|
|
|
|
+ duration: 0,
|
|
|
|
+ currentTime: 0,
|
|
|
|
+ });
|
|
|
|
+ const rootComp = helper.findRootComp();
|
|
|
|
+
|
|
|
|
+ const audioRef = ref();
|
|
|
|
+
|
|
|
|
+ const playAudio = (status: boolean) => {
|
|
|
|
+ if (status) {
|
|
|
|
+ audioRef.value.play();
|
|
|
|
+ } else {
|
|
|
|
+ audioRef.value.pause();
|
|
|
|
+ audioRest();
|
|
|
|
+ }
|
|
|
|
+ state.playStatus = status;
|
|
|
|
+ };
|
|
|
|
+ watch(
|
|
|
|
+ () => rootComp?.value.music,
|
|
|
|
+ () => {
|
|
|
|
+ audioRest();
|
|
|
|
+ }
|
|
|
|
+ );
|
|
|
|
+ const audioRest = () => {
|
|
|
|
+ state.playStatus = false;
|
|
|
|
+ audioRef.value.currentTime = 0;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const timeChange = (v: number) => {
|
|
|
|
+ state.currentTime = v;
|
|
|
|
+ audioRef.value.currentTime = v;
|
|
|
|
+ };
|
|
|
|
+ return () => {
|
|
|
|
+ const music = rootComp?.value.music;
|
|
|
|
+ return (
|
|
|
|
+ <div class={store.isEditMode ? MusicEditStyle : MusicStyle}>
|
|
|
|
+ {store.isEditMode ? (
|
|
|
|
+ <AudioPlayer
|
|
|
|
+ key={music}
|
|
|
|
+ playStatus={state.playStatus}
|
|
|
|
+ onStatus={playAudio}
|
|
|
|
+ onDuration={timeChange}
|
|
|
|
+ duration={state.duration}
|
|
|
|
+ currentTime={state.currentTime}
|
|
|
|
+ />
|
|
|
|
+ ) : (
|
|
|
|
+ <div
|
|
|
|
+ onClick={() => {
|
|
|
|
+ playAudio(!state.playStatus);
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ <IconMusic />
|
|
|
|
+ </div>
|
|
|
|
+ )}
|
|
|
|
+ {music}
|
|
|
|
+ <audio
|
|
|
|
+ controls={false}
|
|
|
|
+ ref={audioRef}
|
|
|
|
+ key={music}
|
|
|
|
+ autoplay={store.isEditMode ? false : true}
|
|
|
|
+ loop={store.isEditMode ? false : true}
|
|
|
|
+ onLoadedmetadata={(e: any) => {
|
|
|
|
+ state.duration = e.target?.duration;
|
|
|
|
+ }}
|
|
|
|
+ onTimeupdate={(e: any) => {
|
|
|
|
+ state.currentTime = e.target.currentTime;
|
|
|
|
+ }}
|
|
|
|
+ onEnded={() => {
|
|
|
|
+ audioRest();
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ <source src={music} type="audio/mpeg" />
|
|
|
|
+ <p>你的浏览器不支持音频播放</p>
|
|
|
|
+ </audio>
|
|
|
|
+ </div>
|
|
|
|
+ );
|
|
|
|
+ };
|
|
|
|
+ },
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+const AudioPlayer = defineComponent({
|
|
|
|
+ props: {
|
|
|
|
+ playStatus: bool(),
|
|
|
|
+ currentTime: number(),
|
|
|
|
+ duration: number(),
|
|
|
|
+ },
|
|
|
|
+ emits: ["status", "duration"],
|
|
|
|
+ setup(props, { emit }) {
|
|
|
|
+ const audioControl = (playStatus: boolean) => {
|
|
|
|
+ emit("status", playStatus);
|
|
|
|
+ };
|
|
|
|
+ const durationChange = (v: any) => {
|
|
|
|
+ emit("duration", v);
|
|
|
|
+ };
|
|
|
|
+ const transTime = (value?: number) => {
|
|
|
|
+ let time = "";
|
|
|
|
+ if (!value) {
|
|
|
|
+ return "00:00";
|
|
|
|
+ }
|
|
|
|
+ const h = parseInt(`${value / 3600}`);
|
|
|
|
+ value %= 3600;
|
|
|
|
+ let m = parseInt(`${value / 60}`);
|
|
|
|
+ let s = parseInt(`${value % 60}`);
|
|
|
|
+ if (h > 0) {
|
|
|
|
+ time = formatTime(h + ":" + m + ":" + s);
|
|
|
|
+ } else {
|
|
|
|
+ time = formatTime(m + ":" + s);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return time;
|
|
|
|
+ };
|
|
|
|
+ const formatTime = (value: string) => {
|
|
|
|
+ let time = "";
|
|
|
|
+ const s = value.split(":");
|
|
|
|
+ let i = 0;
|
|
|
|
+ for (; i < s.length - 1; i++) {
|
|
|
|
+ time += s[i].length === 1 ? "0" + s[i] : s[i];
|
|
|
|
+ time += ":";
|
|
|
|
+ }
|
|
|
|
+ time += s[i].length === 1 ? "0" + s[i] : s[i];
|
|
|
|
+
|
|
|
|
+ return time;
|
|
|
|
+ };
|
|
|
|
+ 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={props.duration}
|
|
|
|
+ value={props.currentTime}
|
|
|
|
+ onChange={durationChange}
|
|
|
|
+ ></Slider>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ {transTime(props.currentTime)}/{transTime(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: 0;
|
|
|
|
+ left: 0;
|
|
|
|
+ z-index: 999;
|
|
|
|
+`;
|