diff --git a/src/mitt.ts b/src/mitt.ts index a17d805..9e1fa6f 100644 --- a/src/mitt.ts +++ b/src/mitt.ts @@ -1,8 +1,15 @@ import mitt, { Emitter } from 'mitt' +import { Music } from './store/useMusicRequest'; declare type MittType = { onOpenTemplateSettings: { template: string, + + }, + onMusicRequestPlayerEnded: { + music: Music + } + onMusicRequestPlayNextWaitingMusic: { } }; diff --git a/src/router/index.ts b/src/router/index.ts index 6b7ee05..b8ccbaf 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -226,7 +226,7 @@ const routes: Array = [ name: 'manage-musicRequest', component: () => import('@/views/open_live/MusicRequest.vue'), meta: { - title: '点歌 (放歌', + title: '点歌 (点播', keepAlive: true, danmaku: true, }, diff --git a/src/store/useMusicRequest.ts b/src/store/useMusicRequest.ts new file mode 100644 index 0000000..6f517ec --- /dev/null +++ b/src/store/useMusicRequest.ts @@ -0,0 +1,160 @@ +import { DanmakuUserInfo, SongFrom, SongsInfo } from '@/api/api-models' +import { useStorage } from '@vueuse/core' +import { useMessage } from 'naive-ui' +import { defineStore } from 'pinia' +import { computed, ref } from 'vue' + +export type WaitMusicInfo = { + from: DanmakuUserInfo + music: SongsInfo +} +export type Music = { + id: number + title: string + artist: string + src: string + pic: string + lrc: string +} +export type MusicRequestSettings = { + playMusicWhenFree: boolean + + repeat: 'repeat-one' | 'repeat-all' | 'no-repeat' + listMaxHeight: string + shuffle: boolean + volume: number + + orderPrefix: string + orderCooldown?: number + orderMusicFirst: boolean + platform: 'netease' | 'kugou' + deviceId?: string + + blacklist: string[] +} + +export const useMusicRequestProvider = defineStore('MusicRequest', () => { + const waitingMusics = useStorage('Setting.MusicRequest.Waiting', []) + const originMusics = ref([]) + const aplayerMusics = computed(() => originMusics.value.map((m) => songToMusic(m))) + const currentMusic = ref({ + id: -1, + title: '', + artist: '', + src: '', + pic: '', + lrc: '', + } as Music) + const currentOriginMusic = ref() + const isPlayingOrderMusic = ref(false) + const aplayerRef = ref() + const settings = useStorage('Setting.MusicRequest', { + playMusicWhenFree: true, + repeat: 'repeat-all', + listMaxHeight: '300', + shuffle: true, + volume: 0.5, + + orderPrefix: '点歌', + orderCooldown: 600, + orderMusicFirst: true, + platform: 'netease', + + blacklist: [], + }) + + const message = useMessage() + + function addWaitingMusic(info: WaitMusicInfo) { + if ((settings.value.orderMusicFirst && !isPlayingOrderMusic.value) || originMusics.value.length == 0 || aplayerRef.value?.audio.paused) { + playMusic(info.music) + console.log(`正在播放 [${info.from.name}] 点的 ${info.music.name} - ${info.music.author?.join('/')}`) + } else { + waitingMusics.value.push(info) + message.success(`[${info.from.name}] 点了一首 ${info.music.name} - ${info.music.author?.join('/')}`) + } + } + function onMusicEnd() { + if (!playWaitingMusic()) { + isPlayingOrderMusic.value = false + if (currentOriginMusic) { + currentOriginMusic.value = undefined + } + setTimeout(() => { + if (!settings.value.playMusicWhenFree) { + message.info('根据配置,已暂停播放音乐') + currentMusic.value = aplayerMusics.value[0] + pauseMusic() + } + }, 1) + } + } + function onMusicPlay() {} + function playWaitingMusic() { + const info = waitingMusics.value.shift() + if (info) { + message.success(`正在播放 [${info.from.name}] 点的 ${info.music.name}`) + console.log(`正在播放 [${info.from.name}] 点的 ${info.music.name}`) + setTimeout(() => { + isPlayingOrderMusic.value = true + const index = waitingMusics.value.indexOf(info) + if (index > -1) { + waitingMusics.value.splice(index, 1) + } + currentOriginMusic.value = info + aplayerRef.value?.pause() + playMusic(info.music) + }, 10) + return true + } else { + return false + } + } + function playMusic(music: SongsInfo) { + //pauseMusic() + currentMusic.value = songToMusic(music) + aplayerRef.value?.thenPlay() + } + function pauseMusic() { + if (!aplayerRef.value?.audio.paused) { + aplayerRef.value?.pause() + } + } + function songToMusic(s: SongsInfo) { + return { + id: s.id, + title: s.name, + artist: s.author?.join('/'), + src: s.from == SongFrom.Netease ? `https://music.163.com/song/media/outer/url?id=${s.id}.mp3` : s.url, + pic: s.cover ?? '', + lrc: '', + } as Music + } + function setSinkId() { + try { + aplayerRef.value?.audio.setSinkId(settings.value.deviceId ?? 'default') + console.log('设置音频输出设备为 ' + (settings.value.deviceId ?? '默认')) + } catch (err) { + console.error(err) + message.error('设置音频输出设备失败: ' + err) + } + } + + return { + waitingMusics, + originMusics, + aplayerMusics, + currentMusic, + currentOriginMusic, + isPlayingOrderMusic, + settings, + setSinkId, + playWaitingMusic, + playMusic, + addWaitingMusic, + onMusicEnd, + onMusicPlay, + pauseMusic, + aplayerRef, + } +}) diff --git a/src/views/ManageLayout.vue b/src/views/ManageLayout.vue index 59662c4..5866727 100644 --- a/src/views/ManageLayout.vue +++ b/src/views/ManageLayout.vue @@ -5,6 +5,7 @@ import { ThemeType } from '@/api/api-models' import { QueryGetAPI } from '@/api/query' import RegisterAndLogin from '@/components/RegisterAndLogin.vue' import { ACCOUNT_API_URL } from '@/data/constants' +import { useMusicRequestProvider } from '@/store/useMusicRequest' import { CalendarClock24Filled, Chat24Filled, @@ -28,6 +29,7 @@ import { NIcon, NLayout, NLayoutContent, + NLayoutFooter, NLayoutHeader, NLayoutSider, NMenu, @@ -37,12 +39,14 @@ import { NSpace, NSpin, NSwitch, + NTag, NText, NTooltip, useMessage, } from 'naive-ui' -import { computed, h, onMounted, ref } from 'vue' +import { computed, h, onMounted, ref, watch } from 'vue' import { RouterLink, useRoute } from 'vue-router' +import APlayer from 'vue3-aplayer' import DanmakuLayout from './manage/DanmakuLayout.vue' const accountInfo = useAccount() @@ -60,9 +64,18 @@ const type = computed(() => { return '' }) const cookie = useStorage('JWT_Token', '') +const musicRquestStore = useMusicRequestProvider() const canResendEmail = ref(false) +const aplayerHeight = computed(() => { + return musicRquestStore.originMusics.length == 0 ? '0' : '80' +}) +const aplayer = ref() +watch(aplayer, () => { + musicRquestStore.aplayerRef = aplayer.value +}) + function renderIcon(icon: unknown) { return () => h(NIcon, null, { default: () => h(icon as any) }) } @@ -276,7 +289,7 @@ const menuOptions = [ }, ), ]), - default: () => accountInfo.value?.isBiliVerified ? '需要使用直播弹幕的功能' : '你尚未进行 Bilibili 认证, 请前往面板进行绑定', + default: () => (accountInfo.value?.isBiliVerified ? '需要使用直播弹幕的功能' : '你尚未进行 Bilibili 认证, 请前往面板进行绑定'), }, ), key: 'manage-danmaku', @@ -428,82 +441,103 @@ onMounted(() => { - - - - - - - - - -