mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-05-17 02:07:29 +08:00
feat: bili播放优化
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { IBilibiliPlayUrl, IBilibiliVideoDetail } from '@/types/bilibili';
|
||||
import type { IBilibiliPage, IBilibiliPlayUrl, IBilibiliVideoDetail } from '@/types/bilibili';
|
||||
import type { SongResult } from '@/types/music';
|
||||
import { getSetData, isElectron } from '@/utils';
|
||||
import request from '@/utils/request';
|
||||
|
||||
@@ -217,3 +218,227 @@ export const searchAndGetBilibiliAudioUrl = async (keyword: string): Promise<str
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 解析B站ID格式
|
||||
* @param biliId B站ID,可能是字符串格式(bvid--pid--cid)
|
||||
* @returns 解析后的对象 {bvid, pid, cid} 或 null
|
||||
*/
|
||||
export const parseBilibiliId = (
|
||||
biliId: string | number
|
||||
): { bvid: string; pid: string; cid: number } | null => {
|
||||
const strBiliId = String(biliId);
|
||||
|
||||
if (strBiliId.includes('--')) {
|
||||
const [bvid, pid, cid] = strBiliId.split('--');
|
||||
if (!bvid || !pid || !cid) {
|
||||
console.warn(`B站ID格式错误: ${strBiliId}, 正确格式应为 bvid--pid--cid`);
|
||||
return null;
|
||||
}
|
||||
return { bvid, pid, cid: Number(cid) };
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建默认的Artist对象
|
||||
* @param name 艺术家名称
|
||||
* @param id 艺术家ID
|
||||
* @returns Artist对象
|
||||
*/
|
||||
const createDefaultArtist = (name: string, id: number = 0) => ({
|
||||
name,
|
||||
id,
|
||||
picId: 0,
|
||||
img1v1Id: 0,
|
||||
briefDesc: '',
|
||||
img1v1Url: '',
|
||||
albumSize: 0,
|
||||
alias: [],
|
||||
trans: '',
|
||||
musicSize: 0,
|
||||
topicPerson: 0,
|
||||
picUrl: ''
|
||||
});
|
||||
|
||||
/**
|
||||
* 创建默认的Album对象
|
||||
* @param name 专辑名称
|
||||
* @param picUrl 专辑图片URL
|
||||
* @param artistName 艺术家名称
|
||||
* @param artistId 艺术家ID
|
||||
* @returns Album对象
|
||||
*/
|
||||
const createDefaultAlbum = (
|
||||
name: string,
|
||||
picUrl: string,
|
||||
artistName: string,
|
||||
artistId: number = 0
|
||||
) => ({
|
||||
name,
|
||||
picUrl,
|
||||
id: 0,
|
||||
type: '',
|
||||
size: 0,
|
||||
picId: 0,
|
||||
blurPicUrl: '',
|
||||
companyId: 0,
|
||||
pic: 0,
|
||||
publishTime: 0,
|
||||
description: '',
|
||||
tags: '',
|
||||
company: '',
|
||||
briefDesc: '',
|
||||
artist: createDefaultArtist(artistName, artistId),
|
||||
songs: [],
|
||||
alias: [],
|
||||
status: 0,
|
||||
copyrightId: 0,
|
||||
commentThreadId: '',
|
||||
artists: [],
|
||||
subType: '',
|
||||
transName: null,
|
||||
onSale: false,
|
||||
mark: 0,
|
||||
picId_str: ''
|
||||
});
|
||||
|
||||
/**
|
||||
* 创建基础的B站SongResult对象
|
||||
* @param config 配置对象
|
||||
* @returns SongResult对象
|
||||
*/
|
||||
const createBaseBilibiliSong = (config: {
|
||||
id: string | number;
|
||||
name: string;
|
||||
picUrl: string;
|
||||
artistName: string;
|
||||
artistId?: number;
|
||||
albumName: string;
|
||||
bilibiliData?: { bvid: string; cid: number };
|
||||
playMusicUrl?: string;
|
||||
duration?: number;
|
||||
}): SongResult => {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
picUrl,
|
||||
artistName,
|
||||
artistId = 0,
|
||||
albumName,
|
||||
bilibiliData,
|
||||
playMusicUrl,
|
||||
duration
|
||||
} = config;
|
||||
|
||||
const baseResult: SongResult = {
|
||||
id,
|
||||
name,
|
||||
picUrl,
|
||||
ar: [createDefaultArtist(artistName, artistId)],
|
||||
al: createDefaultAlbum(albumName, picUrl, artistName, artistId),
|
||||
count: 0,
|
||||
source: 'bilibili' as const
|
||||
};
|
||||
|
||||
if (bilibiliData) {
|
||||
baseResult.bilibiliData = bilibiliData;
|
||||
}
|
||||
|
||||
if (playMusicUrl) {
|
||||
baseResult.playMusicUrl = playMusicUrl;
|
||||
}
|
||||
|
||||
if (duration !== undefined) {
|
||||
baseResult.duration = duration;
|
||||
}
|
||||
|
||||
return baseResult as SongResult;
|
||||
};
|
||||
|
||||
/**
|
||||
* 从B站视频详情和分P信息创建SongResult对象
|
||||
* @param videoDetail B站视频详情
|
||||
* @param page 分P信息
|
||||
* @param bvid B站视频ID
|
||||
* @returns SongResult对象
|
||||
*/
|
||||
export const createSongFromBilibiliVideo = (
|
||||
videoDetail: IBilibiliVideoDetail,
|
||||
page: IBilibiliPage,
|
||||
bvid: string
|
||||
): SongResult => {
|
||||
const pageName = page.part || '';
|
||||
const title = `${pageName} - ${videoDetail.title}`;
|
||||
const songId = `${bvid}--${page.page}--${page.cid}`;
|
||||
const picUrl = getBilibiliProxyUrl(videoDetail.pic);
|
||||
|
||||
return createBaseBilibiliSong({
|
||||
id: songId,
|
||||
name: title,
|
||||
picUrl,
|
||||
artistName: videoDetail.owner.name,
|
||||
artistId: videoDetail.owner.mid,
|
||||
albumName: videoDetail.title,
|
||||
bilibiliData: {
|
||||
bvid,
|
||||
cid: page.cid
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建简化的SongResult对象(用于搜索结果直接播放)
|
||||
* @param item 搜索结果项
|
||||
* @param audioUrl 音频URL
|
||||
* @returns SongResult对象
|
||||
*/
|
||||
export const createSimpleBilibiliSong = (item: any, audioUrl: string): SongResult => {
|
||||
const duration = typeof item.duration === 'string' ? 0 : item.duration * 1000; // 转换为毫秒
|
||||
|
||||
return createBaseBilibiliSong({
|
||||
id: item.id,
|
||||
name: item.title,
|
||||
picUrl: item.pic,
|
||||
artistName: item.author,
|
||||
albumName: item.title,
|
||||
playMusicUrl: audioUrl,
|
||||
duration
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量处理B站视频,从ID列表获取SongResult列表
|
||||
* @param bilibiliIds B站ID列表
|
||||
* @returns SongResult列表
|
||||
*/
|
||||
export const processBilibiliVideos = async (
|
||||
bilibiliIds: (string | number)[]
|
||||
): Promise<SongResult[]> => {
|
||||
const bilibiliSongs: SongResult[] = [];
|
||||
|
||||
for (const biliId of bilibiliIds) {
|
||||
const parsedId = parseBilibiliId(biliId);
|
||||
if (!parsedId) continue;
|
||||
|
||||
try {
|
||||
const res = await getBilibiliVideoDetail(parsedId.bvid);
|
||||
const videoDetail = res.data;
|
||||
|
||||
// 找到对应的分P
|
||||
const page = videoDetail.pages.find((p) => p.cid === parsedId.cid);
|
||||
if (!page) {
|
||||
console.warn(`未找到对应的分P: cid=${parsedId.cid}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const songData = createSongFromBilibiliVideo(videoDetail, page, parsedId.bvid);
|
||||
bilibiliSongs.push(songData);
|
||||
} catch (error) {
|
||||
console.error(`获取B站视频详情失败 (${biliId}):`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return bilibiliSongs;
|
||||
};
|
||||
|
||||
@@ -19,8 +19,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import type { IBilibiliSearchResult } from '@/types/bilibili';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
item: IBilibiliSearchResult;
|
||||
}>();
|
||||
@@ -39,7 +43,7 @@ const handleClick = () => {
|
||||
const formatNumber = (num?: number) => {
|
||||
if (!num) return '0';
|
||||
if (num >= 10000) {
|
||||
return `${(num / 10000).toFixed(1)}万`;
|
||||
return `${(num / 10000).toFixed(1)}${t('bilibili.player.num')}`;
|
||||
}
|
||||
return num.toString();
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="music-info">
|
||||
<div class="music-content-name">{{ playMusic.name }}</div>
|
||||
<div class="music-content-name" v-html="playMusic.name"></div>
|
||||
<div class="music-content-singer">
|
||||
<n-ellipsis
|
||||
class="text-ellipsis"
|
||||
@@ -98,7 +98,7 @@
|
||||
class="music-info-header"
|
||||
:style="{ textAlign: config.centerLyrics ? 'center' : 'left' }"
|
||||
>
|
||||
<div class="music-info-name">{{ playMusic.name }}</div>
|
||||
<div class="music-info-name" v-html="playMusic.name"></div>
|
||||
<div class="music-info-singer">
|
||||
<span
|
||||
v-for="(item, index) in artistList"
|
||||
@@ -562,7 +562,7 @@ defineExpose({
|
||||
@apply text-center w-[600px];
|
||||
|
||||
.music-content-name {
|
||||
@apply text-4xl mb-4;
|
||||
@apply text-4xl mb-4 line-clamp-2;
|
||||
color: var(--text-color-active);
|
||||
}
|
||||
|
||||
@@ -641,7 +641,7 @@ defineExpose({
|
||||
@apply mb-8;
|
||||
|
||||
.music-info-name {
|
||||
@apply text-4xl font-bold mb-2;
|
||||
@apply text-4xl font-bold mb-2 line-clamp-2;
|
||||
color: var(--text-color-active);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<transition name="fade">
|
||||
<div v-if="showFullLyrics && !isLandscape" class="fullscreen-lyrics" :class="config.theme">
|
||||
<div class="fullscreen-header">
|
||||
<div class="song-title">{{ playMusic.name }}</div>
|
||||
<div class="song-title" v-html="playMusic.name"></div>
|
||||
<div class="artist-name">
|
||||
<span v-for="(item, index) in artistList" :key="index">
|
||||
{{ item.name }}{{ index < artistList.length - 1 ? ' / ' : '' }}
|
||||
@@ -97,7 +97,7 @@
|
||||
<!-- 歌曲信息 -->
|
||||
<div class="song-info">
|
||||
<div class="song-title-container">
|
||||
<h1 class="song-title">{{ playMusic.name }}</h1>
|
||||
<h1 class="song-title" v-html="playMusic.name"></h1>
|
||||
</div>
|
||||
<p class="song-artist">
|
||||
<span
|
||||
@@ -190,7 +190,7 @@
|
||||
<!-- 歌曲信息放置在顶部 -->
|
||||
<div class="landscape-song-info">
|
||||
<div class="flex flex-col flex-1">
|
||||
<h1 class="song-title">{{ playMusic.name }}</h1>
|
||||
<h1 class="song-title" v-html="playMusic.name"></h1>
|
||||
<p class="song-artist">
|
||||
<span
|
||||
v-for="(item, index) in artistList"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 歌曲信息 -->
|
||||
<div class="song-info" @click="setMusicFull">
|
||||
<div class="song-title">{{ playMusic?.name || '未播放' }}</div>
|
||||
<div class="song-title" v-html="playMusic?.name || '未播放'"></div>
|
||||
<div class="song-artist">
|
||||
<span
|
||||
v-for="(artists, artistsindex) in artistList"
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<div class="music-content">
|
||||
<div class="music-content-title flex items-center">
|
||||
<n-ellipsis class="text-ellipsis" line-clamp="1">
|
||||
{{ playMusic?.name || '' }}
|
||||
<p v-html="playMusic?.name || ''"></p>
|
||||
</n-ellipsis>
|
||||
<span v-if="playbackRate !== 1.0" class="playback-rate-badge"> {{ playbackRate }}x </span>
|
||||
</div>
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
<div class="content-wrapper">
|
||||
<div v-if="isLoading" class="loading-wrapper">
|
||||
<n-spin size="large" />
|
||||
<p>听书加载中...</p>
|
||||
<p>{{ t('bilibili.player.loading') }}</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="errorMessage" class="error-wrapper">
|
||||
<i class="ri-error-warning-line text-4xl text-red-500"></i>
|
||||
<p>{{ errorMessage }}</p>
|
||||
<n-button type="primary" @click="loadVideoSource">重试</n-button>
|
||||
<n-button type="primary" @click="loadVideoSource">{{
|
||||
t('bilibili.player.retry')
|
||||
}}</n-button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="videoDetail" class="bilibili-info-wrapper" :class="mainContentAnimation">
|
||||
@@ -36,14 +38,16 @@
|
||||
<template #icon>
|
||||
<i class="ri-play-fill"></i>
|
||||
</template>
|
||||
立即播放
|
||||
{{ t('bilibili.player.playNow') }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="video-info">
|
||||
<div class="title">{{ videoDetail?.title || '加载中...' }}</div>
|
||||
|
||||
<div
|
||||
class="title"
|
||||
v-html="videoDetail?.title || t('bilibili.player.loadingTitle')"
|
||||
></div>
|
||||
<div class="author">
|
||||
<i class="ri-user-line mr-1"></i>
|
||||
<span>{{ videoDetail.owner?.name }}</span>
|
||||
@@ -65,7 +69,13 @@
|
||||
<p>{{ videoDetail.desc }}</p>
|
||||
</div>
|
||||
<div class="duration">
|
||||
<p>总时长: {{ formatTotalDuration(videoDetail.duration) }}</p>
|
||||
<p>
|
||||
{{
|
||||
t('bilibili.player.totalDuration', {
|
||||
duration: formatTotalDuration(videoDetail.duration)
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,7 +86,7 @@
|
||||
:class="partsListAnimation"
|
||||
>
|
||||
<div class="parts-title">
|
||||
分P列表 (共{{ videoDetail.pages.length }}集)
|
||||
{{ t('bilibili.player.partsList', { count: videoDetail.pages.length }) }}
|
||||
<n-spin v-if="partLoading" size="small" class="ml-2" />
|
||||
</div>
|
||||
<div class="parts-list">
|
||||
@@ -104,9 +114,15 @@
|
||||
<script setup lang="ts">
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { getBilibiliPlayUrl, getBilibiliProxyUrl, getBilibiliVideoDetail } from '@/api/bilibili';
|
||||
import {
|
||||
createSongFromBilibiliVideo as createBilibiliSong,
|
||||
getBilibiliPlayUrl,
|
||||
getBilibiliProxyUrl,
|
||||
getBilibiliVideoDetail
|
||||
} from '@/api/bilibili';
|
||||
import { usePlayerStore } from '@/store/modules/player';
|
||||
import type { IBilibiliPage, IBilibiliVideoDetail } from '@/types/bilibili';
|
||||
import type { SongResult } from '@/types/music';
|
||||
@@ -121,6 +137,7 @@ const route = useRoute();
|
||||
const router = useRouter();
|
||||
const message = useMessage();
|
||||
const playerStore = usePlayerStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
// 从路由参数获取bvid
|
||||
const bvid = computed(() => route.params.bvid as string);
|
||||
@@ -165,7 +182,7 @@ onMounted(async () => {
|
||||
if (bvid.value) {
|
||||
await loadVideoDetail(bvid.value);
|
||||
} else {
|
||||
message.error('视频ID无效');
|
||||
message.error(t('bilibili.player.errors.invalidVideoId'));
|
||||
router.back();
|
||||
}
|
||||
});
|
||||
@@ -193,11 +210,11 @@ const loadVideoDetail = async (bvid: string) => {
|
||||
await loadVideoSource();
|
||||
} else {
|
||||
console.log('视频无分P或分P数据为空');
|
||||
errorMessage.value = '无法加载视频分P信息';
|
||||
errorMessage.value = t('bilibili.player.errors.loadPartInfoFailed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取视频详情失败', error);
|
||||
errorMessage.value = '获取视频详情失败';
|
||||
errorMessage.value = t('bilibili.player.errors.loadVideoDetailFailed');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
// 标记初始加载完成
|
||||
@@ -231,33 +248,8 @@ const loadVideoSource = async () => {
|
||||
return currentAudio;
|
||||
}
|
||||
|
||||
// 其他分P创建占位对象,稍后按需加载
|
||||
return {
|
||||
id: `${bvid.value}--${page.page}--${page.cid}`, // 使用bvid--pid--cid作为唯一ID
|
||||
name: `${page.part || ''} - ${videoDetail.value!.title}`,
|
||||
picUrl: getBilibiliProxyUrl(videoDetail.value!.pic),
|
||||
type: 0,
|
||||
canDislike: false,
|
||||
alg: '',
|
||||
source: 'bilibili', // 设置来源为B站
|
||||
song: {
|
||||
name: `${page.part || ''} - ${videoDetail.value!.title}`,
|
||||
id: `${bvid.value}--${page.page}--${page.cid}`,
|
||||
ar: [
|
||||
{
|
||||
name: videoDetail.value!.owner.name,
|
||||
id: videoDetail.value!.owner.mid
|
||||
}
|
||||
],
|
||||
al: {
|
||||
picUrl: getBilibiliProxyUrl(videoDetail.value!.pic)
|
||||
}
|
||||
} as any,
|
||||
bilibiliData: {
|
||||
bvid: bvid.value,
|
||||
cid: page.cid
|
||||
}
|
||||
} as SongResult;
|
||||
// 其他分P创建占位对象,稍后按需加载 - 使用公用方法
|
||||
return createBilibiliSong(videoDetail.value!, page, bvid.value);
|
||||
});
|
||||
console.log('已生成音频列表,共', audioList.value.length, '首');
|
||||
|
||||
@@ -271,7 +263,7 @@ const loadVideoSource = async () => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取音频播放地址失败', error);
|
||||
errorMessage.value = '获取音频播放地址失败';
|
||||
errorMessage.value = t('bilibili.player.errors.loadAudioUrlFailed');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
@@ -282,37 +274,8 @@ const createSongFromBilibiliVideo = (): SongResult => {
|
||||
throw new Error('视频详情未加载');
|
||||
}
|
||||
|
||||
const pageName = currentPage.value.part || '';
|
||||
const title = `${pageName} - ${videoDetail.value.title}`;
|
||||
|
||||
return {
|
||||
id: `${bvid.value}--${currentPage.value.page}--${currentPage.value.cid}`, // 使用bvid--pid--cid作为唯一ID
|
||||
name: title,
|
||||
picUrl: getBilibiliProxyUrl(videoDetail.value.pic),
|
||||
type: 0,
|
||||
canDislike: false,
|
||||
alg: '',
|
||||
// 设置来源为B站
|
||||
source: 'bilibili',
|
||||
// playMusicUrl属性稍后通过loadSongUrl函数添加
|
||||
song: {
|
||||
name: title,
|
||||
id: `${bvid.value}--${currentPage.value.page}--${currentPage.value.cid}`,
|
||||
ar: [
|
||||
{
|
||||
name: videoDetail.value.owner.name,
|
||||
id: videoDetail.value.owner.mid
|
||||
}
|
||||
],
|
||||
al: {
|
||||
picUrl: getBilibiliProxyUrl(videoDetail.value.pic)
|
||||
}
|
||||
} as any,
|
||||
bilibiliData: {
|
||||
bvid: bvid.value,
|
||||
cid: currentPage.value.cid
|
||||
}
|
||||
} as SongResult;
|
||||
// 使用公用方法创建SongResult
|
||||
return createBilibiliSong(videoDetail.value, currentPage.value, bvid.value);
|
||||
};
|
||||
|
||||
const loadSongUrl = async (
|
||||
@@ -368,20 +331,20 @@ const switchPage = async (page: IBilibiliPage) => {
|
||||
playCurrentAudio();
|
||||
} catch (error) {
|
||||
console.error('切换分P时加载音频URL失败:', error);
|
||||
message.error('获取音频地址失败,请重试');
|
||||
message.error(t('bilibili.player.errors.switchPartFailed'));
|
||||
} finally {
|
||||
partLoading.value = false;
|
||||
}
|
||||
} else {
|
||||
console.error('未找到对应的音频项');
|
||||
message.error('未找到对应的音频,请重试');
|
||||
message.error(t('bilibili.player.errors.switchPartFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
const playCurrentAudio = async () => {
|
||||
if (audioList.value.length === 0) {
|
||||
console.error('音频列表为空');
|
||||
errorMessage.value = '音频列表为空,请重试';
|
||||
errorMessage.value = t('bilibili.player.errors.audioListEmpty');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -392,7 +355,7 @@ const playCurrentAudio = async () => {
|
||||
|
||||
if (currentIndex === -1) {
|
||||
console.error('未找到当前分P的音频');
|
||||
errorMessage.value = '未找到当前分P的音频';
|
||||
errorMessage.value = t('bilibili.player.errors.currentPartNotFound');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -428,7 +391,7 @@ const playCurrentAudio = async () => {
|
||||
playerStore.setPlay(currentAudio);
|
||||
|
||||
// 播放后通知用户已开始播放
|
||||
message.success('已开始播放');
|
||||
message.success(t('bilibili.player.playStarted'));
|
||||
} catch (error) {
|
||||
console.error('播放音频失败:', error);
|
||||
errorMessage.value = error instanceof Error ? error.message : '播放失败,请重试';
|
||||
@@ -604,7 +567,7 @@ watch(
|
||||
}
|
||||
|
||||
.parts-list {
|
||||
@apply flex flex-wrap gap-2 max-h-60 overflow-y-auto pb-4;
|
||||
@apply flex flex-wrap gap-2 pb-4;
|
||||
|
||||
.part-item {
|
||||
@apply text-xs mb-2;
|
||||
|
||||
@@ -100,7 +100,7 @@ import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { getBilibiliProxyUrl, getBilibiliVideoDetail } from '@/api/bilibili';
|
||||
import { processBilibiliVideos } from '@/api/bilibili';
|
||||
import { getMusicDetail } from '@/api/music';
|
||||
import SongItem from '@/components/common/SongItem.vue';
|
||||
import { useDownload } from '@/hooks/useDownload';
|
||||
@@ -228,58 +228,8 @@ const getFavoriteSongs = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理B站视频数据
|
||||
const bilibiliSongs: SongResult[] = [];
|
||||
for (const biliId of bilibiliIds) {
|
||||
const strBiliId = String(biliId);
|
||||
console.log(`处理B站ID: ${strBiliId}`);
|
||||
|
||||
if (strBiliId.includes('--')) {
|
||||
// 从ID中提取B站视频信息 (bvid--pid--cid格式)
|
||||
try {
|
||||
const [bvid, pid, cid] = strBiliId.split('--');
|
||||
if (!bvid || !pid || !cid) {
|
||||
console.warn(`B站ID格式错误: ${strBiliId}, 正确格式应为 bvid--pid--cid`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const res = await getBilibiliVideoDetail(bvid);
|
||||
const videoDetail = res.data;
|
||||
|
||||
// 找到对应的分P
|
||||
const page = videoDetail.pages.find((p) => p.cid === Number(cid));
|
||||
if (!page) {
|
||||
console.warn(`未找到对应的分P: cid=${cid}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const songData = {
|
||||
id: strBiliId,
|
||||
name: `${page.part || ''} - ${videoDetail.title}`,
|
||||
picUrl: getBilibiliProxyUrl(videoDetail.pic),
|
||||
ar: [
|
||||
{
|
||||
name: videoDetail.owner.name,
|
||||
id: videoDetail.owner.mid
|
||||
}
|
||||
],
|
||||
al: {
|
||||
name: videoDetail.title,
|
||||
picUrl: getBilibiliProxyUrl(videoDetail.pic)
|
||||
},
|
||||
source: 'bilibili',
|
||||
bilibiliData: {
|
||||
bvid,
|
||||
cid: Number(cid)
|
||||
}
|
||||
} as SongResult;
|
||||
|
||||
bilibiliSongs.push(songData);
|
||||
} catch (error) {
|
||||
console.error(`获取B站视频详情失败 (${strBiliId}):`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 处理B站视频数据 - 使用公用方法
|
||||
const bilibiliSongs = await processBilibiliVideos(bilibiliIds);
|
||||
|
||||
console.log('获取数据统计:', {
|
||||
neteaseSongs: neteaseSongs.length,
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { getBilibiliProxyUrl, getBilibiliVideoDetail } from '@/api/bilibili';
|
||||
import { processBilibiliVideos } from '@/api/bilibili';
|
||||
import { getMusicDetail } from '@/api/music';
|
||||
import SongItem from '@/components/common/SongItem.vue';
|
||||
import { useMusicHistory } from '@/hooks/MusicHistoryHook';
|
||||
@@ -90,45 +90,24 @@ const getHistorySongs = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理B站视频
|
||||
const bilibiliSongs: SongResult[] = [];
|
||||
for (const item of bilibiliItems) {
|
||||
try {
|
||||
const bvid = item.bilibiliData?.bvid;
|
||||
if (!bvid) continue;
|
||||
// 处理B站视频 - 使用公用方法
|
||||
const bilibiliIds = bilibiliItems
|
||||
.map((item) => `${item.bilibiliData?.bvid}--1--${item.bilibiliData?.cid}`)
|
||||
.filter((id) => id && !id.includes('undefined'));
|
||||
|
||||
const res = await getBilibiliVideoDetail(bvid);
|
||||
const videoDetail = res.data;
|
||||
const bilibiliSongs = await processBilibiliVideos(bilibiliIds);
|
||||
|
||||
// 找到对应的分P
|
||||
const page = videoDetail.pages.find((p) => p.cid === item.bilibiliData?.cid);
|
||||
if (!page) continue;
|
||||
|
||||
bilibiliSongs.push({
|
||||
id: `${bvid}--${page.page}--${page.cid}`,
|
||||
name: `${page.part || ''} - ${videoDetail.title}`,
|
||||
picUrl: getBilibiliProxyUrl(videoDetail.pic),
|
||||
ar: [
|
||||
{
|
||||
name: videoDetail.owner.name,
|
||||
id: videoDetail.owner.mid
|
||||
}
|
||||
],
|
||||
al: {
|
||||
name: videoDetail.title,
|
||||
picUrl: getBilibiliProxyUrl(videoDetail.pic)
|
||||
},
|
||||
source: 'bilibili',
|
||||
count: item.count || 0,
|
||||
bilibiliData: {
|
||||
bvid,
|
||||
cid: page.cid
|
||||
}
|
||||
} as SongResult);
|
||||
} catch (error) {
|
||||
console.error('获取B站视频详情失败:', error);
|
||||
// 添加count信息
|
||||
bilibiliSongs.forEach((song) => {
|
||||
const historyItem = bilibiliItems.find(
|
||||
(item) =>
|
||||
item.bilibiliData?.bvid === song.bilibiliData?.bvid &&
|
||||
item.bilibiliData?.cid === song.bilibiliData?.cid
|
||||
);
|
||||
if (historyItem) {
|
||||
song.count = historyItem.count || 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 合并两种来源的数据,并保持原有顺序
|
||||
const newSongs = currentPageItems
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<i class="ri-add-line"></i>
|
||||
</div>
|
||||
</n-button-group>
|
||||
<div>{{ staticData.playMusic.name }}</div>
|
||||
<div v-html="staticData.playMusic.name"></div>
|
||||
</div>
|
||||
<!-- 添加播放控制按钮 -->
|
||||
<div class="play-controls">
|
||||
|
||||
@@ -133,7 +133,13 @@ import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { getBilibiliProxyUrl, searchBilibili } from '@/api/bilibili';
|
||||
import {
|
||||
createSimpleBilibiliSong,
|
||||
getBilibiliAudioUrl,
|
||||
getBilibiliProxyUrl,
|
||||
getBilibiliVideoDetail,
|
||||
searchBilibili
|
||||
} from '@/api/bilibili';
|
||||
import { getHotSearch } from '@/api/home';
|
||||
import { getSearch } from '@/api/search';
|
||||
import BilibiliItem from '@/components/common/BilibiliItem.vue';
|
||||
@@ -424,9 +430,35 @@ const handleSearchHistory = (item: { keyword: string; type: number }) => {
|
||||
};
|
||||
|
||||
// 处理B站视频播放
|
||||
const handlePlayBilibili = (item: IBilibiliSearchResult) => {
|
||||
// 使用路由导航到B站播放页面
|
||||
router.push(`/bilibili/${item.bvid}`);
|
||||
const handlePlayBilibili = async (item: IBilibiliSearchResult) => {
|
||||
try {
|
||||
// 获取视频详情以判断是否为单个视频
|
||||
const videoDetail = await getBilibiliVideoDetail(item.bvid);
|
||||
const pages = videoDetail.data.pages;
|
||||
|
||||
// 如果是单个视频(只有一个分P),直接播放
|
||||
if (pages && pages.length === 1) {
|
||||
// 获取音频URL并播放
|
||||
const audioUrl = await getBilibiliAudioUrl(item.bvid, pages[0].cid);
|
||||
|
||||
// 使用公用方法创建播放项目
|
||||
const playItem = createSimpleBilibiliSong(item, audioUrl);
|
||||
playItem.bilibiliData = {
|
||||
bvid: item.bvid,
|
||||
cid: pages[0].cid
|
||||
};
|
||||
|
||||
// 添加到播放列表并开始播放
|
||||
playerStore.setPlay(playItem);
|
||||
} else {
|
||||
// 多P视频,跳转到详情页面
|
||||
router.push(`/bilibili/${item.bvid}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理B站视频播放失败:', error);
|
||||
// 出错时回退到原来的逻辑,跳转详情页
|
||||
router.push(`/bilibili/${item.bvid}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePlayAll = () => {
|
||||
|
||||
Reference in New Issue
Block a user