完成 video 类型的数据获取&渲染

fix #77
This commit is contained in:
BTMuli
2024-01-14 20:20:46 +08:00
parent 92ea04ef88
commit 8953d9f5c4
6 changed files with 352 additions and 5 deletions

View File

@@ -9,6 +9,7 @@ import TpLinkCard from "./tp-linkCard.vue";
import TpMention from "./tp-mention.vue";
import TpText from "./tp-text.vue";
import TpUnknown from "./tp-unknown.vue";
import TpVideo from "./tp-video.vue";
import TpVillaCard from "./tp-villaCard.vue";
import TpVod from "./tp-vod.vue";
import TpVote from "./tp-vote.vue";
@@ -26,8 +27,8 @@ function getTpName(tp: TGApp.Plugins.Mys.SctPost.Base) {
return TpImage;
} else if ("vod" in tp.insert) {
return TpVod;
// } else if ("video" in tp.insert) {
// return TpVideo;
} else if ("video" in tp.insert) {
return TpVideo;
} else if ("backup_text" in tp.insert) {
return TpBackupText;
} else if ("link_card" in tp.insert) {

View File

@@ -1,12 +1,30 @@
<template>
<div class="tp-video-box">
{{ props.data }}
<!-- todo https://socialsisteryi.github.io/bilibili-API-collect/docs/video/videostream_url.html#%E8%A7%86%E9%A2%91%E4%BC%B4%E9%9F%B3%E9%9F%B3%E8%B4%A8%E4%BB%A3%E7%A0%81 -->
<iframe
class="tp-video-container"
data-html2canvas-ignore
:src="props.data.insert.video"
:allowfullscreen="false"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
sandbox="allow-forms allow-same-origin allow-popups allow-presentation"
>
</iframe>
<!-- todo 优化 -->
<div class="tp-video-cover" v-if="videoData">
<img alt="cover" :src="videoData.pic" />
<img src="/source/UI/video_play.svg" alt="icon" />
<span>{{ getVideoTime() }}</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import md5 from "js-md5";
import { onMounted, ref } from "vue";
import Bili from "../../plugins/Bili";
import { saveImgLocal } from "../../utils/TGShare";
// todo https://zhuanlan.zhihu.com/p/450632587
interface TpVideo {
insert: {
video: string;
@@ -19,8 +37,48 @@ interface TpVideoProps {
const props = defineProps<TpVideoProps>();
const videoAspectRatio = ref<number>(16 / 9);
const videoData = ref<TGApp.Plugins.Bili.Video.ViewData>();
console.log("tpVideo", props.data.insert.video);
onMounted(async () => {
const url = new URL(props.data.insert.video);
const aid = url.searchParams.get("aid") ?? undefined;
const bvid = url.searchParams.get("bvid") ?? undefined;
videoData.value = await Bili.video.view(aid, bvid);
if (!videoData.value) {
return;
}
if (!videoData.value?.pic.startsWith("blob")) {
videoData.value.pic = await saveImgLocal(videoData.value?.pic);
}
const meta = videoData.value.dimension;
if (meta.width > meta.height) {
videoAspectRatio.value = meta.width / meta.height;
} else {
videoAspectRatio.value = meta.height / meta.width;
}
});
function getVideoTime(): string {
const duration = videoData.value?.duration ?? 0;
console.log("duration", duration);
const seconds = duration % 60;
const minutes = Math.floor(duration / 60) % 60;
const hours = Math.floor(duration / 3600);
let result = "";
if (hours > 0) {
result += `${hours.toString().padStart(2, "0")}:`;
}
result += `${minutes.toString().padStart(2, "0")}:`;
result += `${seconds.toString().padStart(2, "0")}`;
return result;
}
// 计算 md5
function getVideoHash(): string {
return md5.md5(props.data.insert.video);
}
</script>
<style lang="css" scoped>
.tp-video-box {
@@ -29,4 +87,52 @@ console.log("tpVideo", props.data.insert.video);
margin: 10px auto;
aspect-ratio: v-bind(videoAspectRatio);
}
.tp-video-container {
overflow: hidden;
width: 100%;
max-width: 100%;
border: none;
border-radius: 10px;
aspect-ratio: v-bind(videoAspectRatio);
}
.tp-video-cover {
position: absolute;
z-index: -1;
top: 0;
left: 0;
display: flex;
overflow: hidden;
width: 100%;
align-items: center;
justify-content: center;
border-radius: 10px;
aspect-ratio: v-bind(videoAspectRatio);
}
.tp-video-cover :first-child {
width: 100%;
object-fit: cover;
}
.tp-video-cover :nth-child(2) {
position: absolute;
top: calc(50% - 40px);
left: calc(50% - 40px);
width: 80px;
height: 80px;
}
.tp-video-cover :nth-child(3) {
position: absolute;
right: 10px;
bottom: 10px;
padding: 0 5px;
border-radius: 5px;
background: rgb(0 0 0/50%);
color: var(--tgc-white-4);
font-family: var(--font-title);
font-size: 12px;
}
</style>

15
src/plugins/Bili/index.ts Normal file
View File

@@ -0,0 +1,15 @@
/**
* @file plugins/Bili/index.ts
* @description Bili插件
* @since Beta v0.4.0
*/
import getVideoView from "./request/getVideoView";
const Bili = {
video: {
view: getVideoView,
},
};
export default Bili;

View File

@@ -0,0 +1,37 @@
/**
* @file plugins/Bili/request/getVideoView.ts
* @description Bili插件-获取视频基本信息
* @since Beta v0.4.0
*/
import { http } from "@tauri-apps/api";
/**
* @description 获取视频基本信息
* @since Beta v0.4.0
* @param {string} [aid] 视频AV号
* @param {string} [bvid] 视频BV号
* @returns {Promise<TGApp.Plugins.Bili.Video.ViewData>} 视频基本信息
*/
async function getVideoView(
aid?: string,
bvid?: string,
): Promise<TGApp.Plugins.Bili.Video.ViewData> {
let url = "https://api.bilibili.com/x/web-interface/view?";
if (aid) {
url += `aid=${aid}`;
} else if (bvid) {
url += `bvid=${bvid}`;
} else {
throw new Error("参数错误");
}
return await http
.fetch<TGApp.Plugins.Bili.Video.ViewResponse>(url, {
method: "GET",
})
.then((res) => {
return res.data.data;
});
}
export default getVideoView;

30
src/plugins/Bili/types/Base.d.ts vendored Normal file
View File

@@ -0,0 +1,30 @@
/**
* @file plugins/Bili/types/Base.d.ts
* @description Bili 插件基础类型定义文件
* @since Beta v0.4.0
*/
/**
* @description Bili 插件基础类型
* @since Beta v0.4.0
* @namespace Base
* @memberof TGApp.Plugins.Bili
*/
declare namespace TGApp.Plugins.Bili.Base {
/**
* @description Bili Response 统一接口
* @since Beta v0.4.0
* @interface Response
* @property {number} code 状态码
* @property {string} message 状态信息
* @property {number} ttl ttl
* @property {any} data 数据
* @return Response
*/
interface Response {
code: number;
message: string;
ttl: number;
data: any;
}
}

158
src/plugins/Bili/types/Video.d.ts vendored Normal file
View File

@@ -0,0 +1,158 @@
/**
* @file plugins/Bili/types/Video.d.ts
* @description Bili 插件视频类型定义文件
* @since Beta v0.4.0
*/
/**
* @description Bili 插件视频类型
* @since Beta v0.4.0
* @namespace Video
* @memberof TGApp.Plugins.Bili
*/
declare namespace TGApp.Plugins.Bili.Video {
/**
* @description Bili 视频基本信息返回
* @since Beta v0.4.0
* @interface ViewResponse
* @extends {TGApp.Plugins.Bili.Base.Response}
* @property {ViewData} data 视频基本信息
* @return ViewResponse
*/
interface ViewResponse extends TGApp.Plugins.Bili.Base.Response {
data: ViewData;
}
/**
* @description Bili 视频基本信息
* @since Beta v0.4.0
* @see https://api.bilibili.com/x/web-interface/view?aid=540893019
* @interface ViewData
* @property {string} bvid 视频BV号
* @property {number} aid 视频AV号
* @property {number} videos 视频分P数量
* @property {number} tid 视频分区ID
* @property {string} tname 视频分区名称
* @property {number} copyright 版权信息 // 1为原创
* @property {string} pic 视频封面图片地址
* @property {string} title 视频标题
* @property {string} pubdate 视频发布时间,时间戳(秒)
* @property {number} ctime 视频创建时间,时间戳(秒)
* @property {string} desc 视频简介
* @property {string} desc_v2.raw_text 视频简介(纯文本)
* @property {number} desc_v2.type 视频简介类型
* @property {number} desc_v2.biz_id 视频简介ID
* @property {number} state 视频状态
* @property {number} duration 视频时长
* @property {unknown} rights 视频权限
* @property {number} owner.mid 视频作者UID
* @property {string} owner.name 视频作者昵称
* @property {string} owner.face 视频作者头像
* @property {number} stat.aid 视频AV号
* @property {number} stat.view 视频播放量
* @property {number} stat.danmaku 视频弹幕数量
* @property {number} stat.reply 视频评论数量
* @property {number} stat.favorite 视频收藏数量
* @property {number} stat.coin 视频硬币数量
* @property {number} stat.share 视频分享数量
* @property {number} stat.now_rank 视频当前排名
* @property {number} stat.his_rank 视频历史最高排名
* @property {number} stat.like 视频点赞数量
* @property {number} stat.dislike 视频点踩数量
* @property {string} stat.evaluation 视频评分
* @property {number} stat.vt 视频投稿类型
* @property {unknown} argue_info 视频争议信息
* @property {string} dynamic 视频动态信息
* @property {number} cid 视频弹幕ID
* @property {number} dimension.width 视频宽度
* @property {number} dimension.height 视频高度
* @property {number} dimension.rotate 视频旋转角度
* @property {unknown} premiere 视频预告信息
* @property {number} teenage_mode 视频青少年模式
* @property {boolean} is_chargeable_season 视频是否为付费分P
* @property {boolean} is_story 视频是否为剧集
* @property {boolean} is_upower_exclusive 视频是否为大会员专享
* @property {boolean} is_upower_play 视频是否为大会员免广告
* @property {boolean} is_upower_preview 视频是否为大会员免前置广告
* @property {number} enable_vt 视频是否开启投稿
* @property {string} vt_display 视频投稿类型显示
* @property {boolean} no_cache 视频是否不缓存
* @property {unknown} pages 视频分P信息
* @property {unknown} subtitle 视频字幕信息
* @property {boolean} is_season_display 视频是否为剧集
* @property {unknown} user_garb 视频用户抽奖信息
* @property {unknown} honor_reply 视频荣誉信息
* @property {unknown} like_icon 视频点赞图标信息
* @property {boolean} need_jump_bv 视频是否需要跳转BV号
* @property {boolean} disable_show_up_info 视频是否禁止显示UP主信息
* @property {boolean} is_story_play 视频是否为剧集免广告
* @return ViewData
*/
interface ViewData {
bvid: string;
aid: number;
videos: number;
tid: number;
tname: string;
pic: string;
title: string;
pubdate: string;
ctime: number;
desc: string;
desc_v2: {
raw_text: string;
type: number;
biz_id: number;
};
state: number;
duration: number;
rights: unknown;
owner: {
mid: number;
name: string;
face: string;
};
stat: {
aid: number;
view: number;
danmaku: number;
reply: number;
favorite: number;
coin: number;
share: number;
now_rank: number;
his_rank: number;
like: number;
dislike: number;
evaluation: string;
vt: number;
};
argue_info: unknown;
dynamic: string;
cid: number;
dimension: {
width: number;
height: number;
rotate: number;
};
premiere: unknown;
teenage_mode: number;
is_chargeable_season: boolean;
is_story: boolean;
is_upower_exclusive: boolean;
is_upower_play: boolean;
is_upower_preview: boolean;
enable_vt: number;
vt_display: string;
no_cache: boolean;
pages: unknown;
subtitle: unknown;
is_season_display: boolean;
user_garb: unknown;
honor_reply: unknown;
like_icon: unknown;
need_jump_bv: boolean;
disable_show_up_info: boolean;
is_story_play: boolean;
}
}