UNPKG

@hekeh/easy-audio-player-vue

Version:
559 lines (558 loc) 23.5 kB
(function() { "use strict"; try { if (typeof document != "undefined") { var elementStyle = document.createElement("style"); elementStyle.id = "easy-audio-player-vue-style"; elementStyle.appendChild(document.createTextNode("html {\n --easy-audio-player-text-color: #566574;\n --easy-audio-player-bg-color: #fff;\n --easy-audio-player-icon-color: #566574;\n --easy-audio-player-primary-color: #4a90e2;\n --easy-audio-player-height: 56px;\n}\n\n.easy-audio-player {\n width: 100%;\n min-width: 300px;\n height: var(--easy-audio-player-height);\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 0 15px;\n background-color: var(--easy-audio-player-background-color);\n border-radius: 4px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.07);\n user-select: none;\n cursor: default;\n}\n.easy-audio-player audio {\n display: none !important;\n}\n.easy-audio-player .easy-audio-player-controls {\n display: flex;\n align-items: center;\n width: 100%;\n gap: 8px;\n}\n.easy-audio-player .easy-audio-player-pause-btn {\n display: flex;\n justify-content: center;\n align-items: center;\n height: 28px;\n padding: 0 2px;\n background: transparent;\n border: none;\n cursor: pointer;\n}\n.easy-audio-player .easy-audio-player-pause-btn .easy-audio-player-pause-btn__icon {\n width: 12px;\n height: 16px;\n fill: var(--easy-audio-player-icon-color);\n}\n.easy-audio-player .easy-audio-player-pause-btn:hover {\n opacity: 0.8;\n}\n.easy-audio-player .easy-audio-player-pause-btn.easy-audio-player-pause-btn--loading {\n opacity: 0.5;\n cursor: wait;\n}\n.easy-audio-player .easy-audio-player-right-controls {\n position: relative;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.easy-audio-player .easy-audio-player-right-controls .easy-audio-player-control-btn {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 0;\n background: none;\n border: none;\n cursor: pointer;\n}\n.easy-audio-player .easy-audio-player-right-controls .easy-audio-player-control-btn .easy-audio-player-control-btn__icon {\n width: 18px;\n height: 18px;\n fill: var(--easy-audio-player-icon-color);\n color: var(--easy-audio-player-icon-color);\n}\n.easy-audio-player .easy-audio-player-right-controls .easy-audio-player-control-btn:hover {\n opacity: 0.8;\n}\n.easy-audio-player svg {\n vertical-align: middle;\n}\n.easy-audio-player svg:focus {\n outline: none;\n}\n\n.easy-audio-player .easy-audio-player-progress {\n flex: 1;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.easy-audio-player .easy-audio-player-progress .easy-audio-player-progress-bar__wrapper {\n flex: 1;\n cursor: pointer;\n padding: 8px 0;\n}\n.easy-audio-player .easy-audio-player-progress .easy-audio-player-progress-bar {\n height: 4px;\n border-radius: 2px;\n background-color: #e4e4e4;\n}\n.easy-audio-player .easy-audio-player-progress .easy-audio-player-progress-bar .easy-audio-player-progress-bar__fill {\n height: 100%;\n background-color: var(--easy-audio-player-primary-color);\n border-radius: 2px;\n position: relative;\n transition: width 0.1s ease-in-out;\n width: 0;\n}\n.easy-audio-player .easy-audio-player-progress .easy-audio-player-progress-bar .easy-audio-player-progress-bar__pin {\n width: 12px;\n height: 12px;\n background-color: var(--easy-audio-player-primary-color);\n border-radius: 50%;\n position: absolute;\n right: -6px;\n top: 50%;\n transform: translateY(-50%);\n cursor: grab;\n}\n.easy-audio-player .easy-audio-player-progress .easy-audio-player-progress-bar__pin:active {\n cursor: grabbing;\n}\n.easy-audio-player .easy-audio-player-progress__current-time,\n.easy-audio-player .easy-audio-player-progress__total-time {\n font-size: 12px;\n color: var(--easy-audio-player-text-color);\n width: fit-content;\n padding: 0 2px;\n}\n\n.easy-audio-player-volume__popover {\n min-width: unset !important;\n padding: 10px 0 8px 0 !important;\n background: #fff;\n border-radius: 4px;\n border: 1px solid #e4e7ed;\n}\n.easy-audio-player-volume__popover .easy-audio-player-volume-bar__wrapper {\n height: 100px;\n width: 20px;\n display: flex;\n justify-content: center;\n}\n.easy-audio-player-volume__popover .easy-audio-player-volume-bar {\n width: 4px;\n height: 100%;\n background: #e4e4e4;\n border-radius: 2px;\n position: relative;\n cursor: pointer;\n}\n.easy-audio-player-volume__popover .easy-audio-player-volume-bar .easy-audio-player-volume-bar__fill {\n position: absolute;\n bottom: 0;\n width: 100%;\n background: var(--easy-audio-player-primary-color);\n border-radius: 2px;\n transition: height 0.1s ease-in-out;\n}\n.easy-audio-player-volume__popover .easy-audio-player-volume-bar .easy-audio-player-volume-bar__pin {\n width: 10px;\n height: 10px;\n background: var(--easy-audio-player-primary-color);\n border-radius: 50%;\n position: absolute;\n right: -3px;\n top: -5px;\n cursor: grab;\n}\n.easy-audio-player-volume__popover .easy-audio-player-volume-bar .easy-audio-player-volume-bar__pin:active {\n cursor: grabbing;\n}")); document.head.appendChild(elementStyle); } } catch (e) { console.error("vite-plugin-css-injected-by-js", e); } })(); import { defineComponent, toRef, ref, watch, onBeforeUnmount, openBlock, createElementBlock, normalizeClass, createElementVNode, toDisplayString, normalizeStyle, withModifiers, computed, resolveComponent, createBlock, withCtx, Fragment, onMounted, useId, createVNode, createCommentVNode } from "vue"; import { ElPopover } from "@hekeh/el-popover"; const namespace = "easy-audio-player"; function formatTime(time) { const minutes = Math.floor(time / 60); const seconds = Math.floor(time % 60); return `${minutes}:${seconds.toString().padStart(2, "0")}`; } function bem(block, element, modifier) { return `${namespace}-${block}${element ? `__${element}` : ""}${modifier ? `--${modifier}` : ""}`; } function useProcess(player) { const progressBar = ref(null); const progressBarPin = ref(null); const currentTime = ref("00:00"); const totalTime = ref("00:00"); const isDragging = ref(false); const percent = ref(0); const updateProgress = () => { if (!player.value || !progressBar.value || isDragging.value) { return; } const current = player.value.currentTime; const duration = player.value.duration; percent.value = current / duration; currentTime.value = formatTime(current); }; const handleProgressBarClick = (event) => { var _a; if (!player.value || !((_a = progressBar.value) == null ? void 0 : _a.parentElement)) { return; } const bounds = progressBar.value.parentElement.getBoundingClientRect(); const x = event.clientX - bounds.left; const width = bounds.width; percent.value = x / width; player.value.currentTime = percent.value * player.value.duration; }; const handleDraggingProcess = (event) => { var _a; if (!player.value || !((_a = progressBar.value) == null ? void 0 : _a.parentElement)) { return; } const bounds = progressBar.value.parentElement.getBoundingClientRect(); const x = Math.max(0, Math.min(event.clientX - bounds.left, bounds.width)); percent.value = x / bounds.width; player.value.currentTime = percent.value * player.value.duration; }; const stopDraggingProcess = () => { isDragging.value = false; removeDraggingProcessListeners(); }; const removeDraggingProcessListeners = () => { document.removeEventListener("mousemove", handleDraggingProcess); document.removeEventListener("mouseup", stopDraggingProcess); }; const startDraggingProcess = (event) => { event.stopPropagation(); event.preventDefault(); isDragging.value = true; document.addEventListener("mousemove", handleDraggingProcess); document.addEventListener("mouseup", stopDraggingProcess); }; const playerEvents = { timeupdate: updateProgress, loadedmetadata: () => { var _a; totalTime.value = formatTime(((_a = player.value) == null ? void 0 : _a.duration) || 0); } }; watch( () => player.value, (newPlayer, oldPlayer) => { if (oldPlayer) { Object.entries(playerEvents).forEach(([event, handler]) => { oldPlayer.removeEventListener(event, handler); }); } if (newPlayer) { Object.entries(playerEvents).forEach(([event, handler]) => { newPlayer.addEventListener(event, handler); }); } } ); onBeforeUnmount(() => { const playerDom = player.value; if (playerDom) { Object.entries(playerEvents).forEach(([event, handler]) => { playerDom.removeEventListener(event, handler); }); } removeDraggingProcessListeners(); }); return { progressBar, progressBarPin, currentTime, totalTime, percent, updateProgress, handleProgressBarClick, startDraggingProcess }; } const PlayerProgress$1 = defineComponent({ name: "PlayerProgress", props: { player: { type: [Object, null], required: true } }, setup(props) { const { progressBar, progressBarPin, currentTime, totalTime, percent, updateProgress, handleProgressBarClick, startDraggingProcess } = useProcess(toRef(props, "player")); return { progressBar, progressBarPin, currentTime, totalTime, percent, updateProgress, handleProgressBarClick, startDraggingProcess, bem }; } }); const _export_sfc = (sfc, props) => { const target = sfc.__vccOpts || sfc; for (const [key, val] of props) { target[key] = val; } return target; }; function _sfc_render$2(_ctx, _cache, $props, $setup, $data, $options) { return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.bem("progress")) }, [ createElementVNode("span", { class: normalizeClass(_ctx.bem("progress", "current-time")) }, toDisplayString(_ctx.currentTime), 3), createElementVNode("div", { class: normalizeClass(_ctx.bem("progress-bar", "wrapper")), onClick: _cache[2] || (_cache[2] = (...args) => _ctx.handleProgressBarClick && _ctx.handleProgressBarClick(...args)) }, [ createElementVNode("div", { class: normalizeClass(_ctx.bem("progress-bar")) }, [ createElementVNode("div", { ref: "progressBar", class: normalizeClass(_ctx.bem("progress-bar", "fill")), style: normalizeStyle({ width: `${_ctx.percent * 100}%` }) }, [ createElementVNode("div", { ref: "progressBarPin", class: normalizeClass(_ctx.bem("progress-bar", "pin")), onMousedown: _cache[0] || (_cache[0] = (...args) => _ctx.startDraggingProcess && _ctx.startDraggingProcess(...args)), onDragstart: _cache[1] || (_cache[1] = withModifiers(() => { }, ["prevent"])) }, null, 34) ], 6) ], 2) ], 2), createElementVNode("span", { class: normalizeClass(_ctx.bem("progress", "total-time")) }, toDisplayString(_ctx.totalTime), 3) ], 2); } const PlayerProgress = /* @__PURE__ */ _export_sfc(PlayerProgress$1, [["render", _sfc_render$2]]); function useVolume(setVolume) { const volumeBar = ref(null); const volumeBarPin = ref(null); const volumeBarWrapper = ref(null); const volumePercentage = ref(1); const isVolumeOpen = ref(true); const isDragging = ref(false); const handleVolumeBarClick = (event) => { var _a; if (!((_a = volumeBar.value) == null ? void 0 : _a.parentElement) || isDragging.value) { return; } isVolumeOpen.value = true; const bounds = volumeBar.value.parentElement.getBoundingClientRect(); const y = bounds.bottom - event.clientY; const height = bounds.height; const percentage = Math.max(0, Math.min(y / height, 1)); volumePercentage.value = percentage; }; const handleDraggingVolume = (event) => { var _a; if (!((_a = volumeBar.value) == null ? void 0 : _a.parentElement)) { return; } const bounds = volumeBar.value.parentElement.getBoundingClientRect(); if (bounds.height === 0) { return; } const y = bounds.bottom - event.clientY; const percentage = Math.round(Math.max(0, Math.min(y / bounds.height * 100, 100))) / 100; volumePercentage.value = percentage; }; const removeDraggingVolumeListeners = () => { document.removeEventListener("mousemove", handleDraggingVolume); document.removeEventListener("mouseup", stopDraggingVolume); }; const stopDraggingVolume = () => { isDragging.value = false; removeDraggingVolumeListeners(); }; const startDraggingVolume = (event) => { event.stopPropagation(); event.preventDefault(); isDragging.value = true; document.addEventListener("mousemove", handleDraggingVolume); document.addEventListener("mouseup", stopDraggingVolume); }; onBeforeUnmount(() => { removeDraggingVolumeListeners(); }); const toggleVolumeOpen = () => { isVolumeOpen.value = !isVolumeOpen.value; }; const volume = computed(() => { return isVolumeOpen.value ? volumePercentage.value : 0; }); watch(volume, (value) => { setVolume(value); }); return { volumeBar, volumeBarPin, volume, handleVolumeBarClick, startDraggingVolume, volumeBarWrapper, toggleVolumeOpen }; } const emits = { "update:volume": (_) => true }; const PlayerVolume$1 = defineComponent({ name: "PlayerVolume", components: { ElPopover }, emits, setup(_, { emit }) { const setVolume = (volume2) => { emit("update:volume", volume2); }; const { volumeBar, volumeBarPin, volumeBarWrapper, volume, handleVolumeBarClick, startDraggingVolume, toggleVolumeOpen } = useVolume(setVolume); return { volumeBar, volumeBarPin, volumeBarWrapper, volume, handleVolumeBarClick, startDraggingVolume, toggleVolumeOpen, bem }; } }); function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) { const _component_ElPopover = resolveComponent("ElPopover"); return openBlock(), createBlock(_component_ElPopover, { placement: "top", trigger: "hover", width: "auto", "show-arrow": false, "popper-class": _ctx.bem("volume", "popover"), offset: 2 }, { reference: withCtx(() => [ createElementVNode("button", { class: normalizeClass(_ctx.bem("control-btn")), onClick: _cache[0] || (_cache[0] = (...args) => _ctx.toggleVolumeOpen && _ctx.toggleVolumeOpen(...args)) }, [ (openBlock(), createElementBlock("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 22 22", class: normalizeClass(_ctx.bem("control-btn", "icon")) }, [ _ctx.volume === 0 ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [ _cache[4] || (_cache[4] = createElementVNode("path", { d: "M15 11a3.998 3.998 0 0 0-2-3.465v2.636l1.865 1.865A4.02 4.02 0 0 0 15 11z" }, null, -1)), _cache[5] || (_cache[5] = createElementVNode("path", { d: "M13.583 5.583A5.998 5.998 0 0 1 17 11a6 6 0 0 1-.585 2.587l1.477 1.477a8.001 8.001 0 0 0-3.446-11.286 1 1 0 0 0-.863 1.805zm5.195 13.195-2.121-2.121-1.414-1.414-1.415-1.415L13 13l-2-2-3.889-3.889-3.889-3.889a.999.999 0 1 0-1.414 1.414L5.172 8H5a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h1l4.188 3.35a.5.5 0 0 0 .812-.39v-3.131l2.587 2.587-.01.005a1 1 0 0 0 .86 1.806c.215-.102.424-.214.627-.333l2.3 2.3a1.001 1.001 0 0 0 1.414-1.416zM11 5.04a.5.5 0 0 0-.813-.39L8.682 5.854 11 8.172V5.04z" }, null, -1)) ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [ _cache[6] || (_cache[6] = createElementVNode("path", { "fill-rule": "evenodd", d: "M10.188 4.65 6 8H5a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h1l4.188 3.35a.5.5 0 0 0 .812-.39V5.04a.498.498 0 0 0-.812-.39zm4.258-.872a1 1 0 0 0-.862 1.804 6.002 6.002 0 0 1-.007 10.838 1 1 0 0 0 .86 1.806A8.001 8.001 0 0 0 19 11a8.001 8.001 0 0 0-4.554-7.222z" }, null, -1)), _cache[7] || (_cache[7] = createElementVNode("path", { d: "M15 11a3.998 3.998 0 0 0-2-3.465v6.93A3.998 3.998 0 0 0 15 11z" }, null, -1)) ], 64)) ], 2)) ], 2) ]), default: withCtx(() => [ createElementVNode("div", { ref: "volumeBarWrapper", class: normalizeClass(_ctx.bem("volume-bar", "wrapper")) }, [ createElementVNode("div", { class: normalizeClass(_ctx.bem("volume-bar")), onClick: _cache[3] || (_cache[3] = (...args) => _ctx.handleVolumeBarClick && _ctx.handleVolumeBarClick(...args)) }, [ createElementVNode("div", { ref: "volumeBar", class: normalizeClass(_ctx.bem("volume-bar", "fill")), style: normalizeStyle({ height: `${_ctx.volume * 100}%` }) }, [ createElementVNode("div", { ref: "volumeBarPin", class: normalizeClass(_ctx.bem("volume-bar", "pin")), onMousedown: _cache[1] || (_cache[1] = (...args) => _ctx.startDraggingVolume && _ctx.startDraggingVolume(...args)), onDragstart: _cache[2] || (_cache[2] = withModifiers(() => { }, ["prevent"])) }, null, 34) ], 6) ], 2) ], 2) ]), _: 1 }, 8, ["popper-class"]); } const PlayerVolume = /* @__PURE__ */ _export_sfc(PlayerVolume$1, [["render", _sfc_render$1]]); const EasyAudioPlayer$1 = defineComponent({ name: "EasyAudioPlayer", components: { PlayerVolume, PlayerProgress }, props: { url: { type: String, required: true }, options: { type: Object, default: () => ({}) } }, setup(props) { const container = ref(null); const player = ref(null); const isPlaying = ref(false); const isLoading = ref(false); const { stopOthersOnPlay = true } = props.options; const onUpdateVolume = (volume) => { if (!player.value) { return; } player.value.volume = volume; }; const closePlayer = () => { var _a; (_a = player.value) == null ? void 0 : _a.pause(); isPlaying.value = false; }; const { stopOtherPlayers } = useStopOtherPlayers(closePlayer); const togglePlay = async () => { if (!player.value) { return; } try { if (player.value.paused) { if (stopOthersOnPlay) { stopOtherPlayers(); } isLoading.value = true; await player.value.play(); isPlaying.value = true; } else { closePlayer(); } } catch (error) { console.error("Play fail:", error); } finally { isLoading.value = false; } }; const playerEvents = { waiting: () => { isLoading.value = true; }, canplay: () => { isLoading.value = false; } }; onMounted(() => { if (!player.value) { return; } Object.entries(playerEvents).forEach(([event, handler]) => { var _a; (_a = player.value) == null ? void 0 : _a.addEventListener(event, handler); }); }); onBeforeUnmount(() => { if (!player.value) { return; } Object.entries(playerEvents).forEach(([event, handler]) => { var _a; (_a = player.value) == null ? void 0 : _a.removeEventListener(event, handler); }); }); const downloadAudio = () => { const link = document.createElement("a"); link.href = props.url; link.download = props.url.split("/").pop() || "audio"; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; return { container, player, isPlaying, isLoading, showDownloadButton: computed( () => props.options.showDownloadButton ?? true ), namespace, togglePlay, downloadAudio, onUpdateVolume, bem }; } }); function useStopOtherPlayers(closePlayer) { const EventName = "stop-easy-audio-player"; const id = useId(); const stopOtherPlayers = () => { document.dispatchEvent( new CustomEvent(EventName, { detail: { excludedId: id } }) ); }; const stopOtherPlayersHandler = (event) => { if (event.detail.excludedId === id) { return; } closePlayer(); }; onMounted(() => { document.addEventListener( EventName, stopOtherPlayersHandler ); }); onBeforeUnmount(() => { document.removeEventListener( EventName, stopOtherPlayersHandler ); }); return { stopOtherPlayers }; } const _hoisted_1 = ["disabled"]; const _hoisted_2 = ["d"]; const _hoisted_3 = ["src"]; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { const _component_PlayerProgress = resolveComponent("PlayerProgress"); const _component_PlayerVolume = resolveComponent("PlayerVolume"); return openBlock(), createElementBlock("div", { ref: "container", class: normalizeClass(_ctx.namespace) }, [ createElementVNode("div", { class: normalizeClass(_ctx.bem("controls")) }, [ createElementVNode("button", { class: normalizeClass([ _ctx.bem("pause-btn"), { [_ctx.bem("pause-btn", "", "loading")]: _ctx.isLoading } ]), disabled: _ctx.isLoading, onClick: _cache[0] || (_cache[0] = (...args) => _ctx.togglePlay && _ctx.togglePlay(...args)) }, [ (openBlock(), createElementBlock("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 18 24", class: normalizeClass(_ctx.bem("pause-btn", "icon")) }, [ createElementVNode("path", { "fill-rule": "evenodd", d: _ctx.isPlaying ? "M0 0h6v24H0zM12 0h6v24h-6z" : "M18 12L0 24V0" }, null, 8, _hoisted_2) ], 2)) ], 10, _hoisted_1), createVNode(_component_PlayerProgress, { player: _ctx.player }, null, 8, ["player"]), createElementVNode("div", { class: normalizeClass(_ctx.bem("right-controls")) }, [ createVNode(_component_PlayerVolume, { "onUpdate:volume": _ctx.onUpdateVolume }, null, 8, ["onUpdate:volume"]), _ctx.showDownloadButton ? (openBlock(), createElementBlock("button", { key: 0, class: normalizeClass(_ctx.bem("control-btn")), onClick: _cache[1] || (_cache[1] = (...args) => _ctx.downloadAudio && _ctx.downloadAudio(...args)) }, [ (openBlock(), createElementBlock("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", class: normalizeClass(_ctx.bem("control-btn", "icon")) }, _cache[2] || (_cache[2] = [ createElementVNode("path", { "fill-rule": "evenodd", d: "M19 9h-4V3H9v6H5l7 8 7-8zM5 18v3h14v-3H5z" }, null, -1) ]), 2)) ], 2)) : createCommentVNode("", true) ], 2), createElementVNode("audio", { ref: "player", src: _ctx.url, preload: "metadata" }, null, 8, _hoisted_3) ], 2) ], 2); } const EasyAudioPlayer = /* @__PURE__ */ _export_sfc(EasyAudioPlayer$1, [["render", _sfc_render]]); export { EasyAudioPlayer as default };