|
|
@@ -63,6 +63,7 @@
|
|
|
<div class="relative rounded-2xl overflow-hidden bg-black">
|
|
|
<div class="aspect-video video-container">
|
|
|
<VideoProcessor
|
|
|
+ ref="videoProcessorRef"
|
|
|
:cover-url="videoInfo.cover"
|
|
|
:m3u8-url="videoInfo.m3u8"
|
|
|
:alt="videoInfo.name"
|
|
|
@@ -75,7 +76,55 @@
|
|
|
@video-loaded="onVideoLoaded"
|
|
|
@error="onVideoProcessorError"
|
|
|
@retry="onVideoProcessorRetry"
|
|
|
+ @play="onVideoPlay"
|
|
|
+ @timeupdate="onVideoTimeUpdate"
|
|
|
+ @seeking="onVideoSeeking"
|
|
|
/>
|
|
|
+
|
|
|
+ <!-- 试看提示 -->
|
|
|
+ <div
|
|
|
+ v-if="isTrialMode"
|
|
|
+ class="absolute top-4 right-4 text-white px-3 py-1.5 text-sm font-medium"
|
|
|
+ >
|
|
|
+ 试看中
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 试看结束遮罩 -->
|
|
|
+ <div
|
|
|
+ v-if="showPurchaseModal"
|
|
|
+ class="absolute inset-0 bg-black/80 flex items-center justify-center z-50"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="bg-white/10 backdrop-blur-sm rounded-2xl p-6 max-w-sm mx-4 text-center"
|
|
|
+ >
|
|
|
+ <div class="text-white text-lg font-semibold mb-4">
|
|
|
+ 试看时间已结束
|
|
|
+ </div>
|
|
|
+ <div class="text-white/70 text-sm mb-6">
|
|
|
+ 购买会员或单独购买本片继续观看
|
|
|
+ </div>
|
|
|
+ <div class="space-y-3">
|
|
|
+ <button
|
|
|
+ @click="purchaseMembership"
|
|
|
+ class="w-full bg-emerald-500 hover:bg-emerald-600 text-white py-3 px-4 rounded-lg font-medium transition"
|
|
|
+ >
|
|
|
+ 购买会员
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ @click="purchaseVideo"
|
|
|
+ class="w-full bg-white/10 hover:bg-white/20 text-white py-3 px-4 rounded-lg font-medium transition"
|
|
|
+ >
|
|
|
+ 单独购买本片
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ @click="showPurchaseModal = false"
|
|
|
+ class="w-full text-white/60 hover:text-white/80 py-2 text-sm transition"
|
|
|
+ >
|
|
|
+ 取消
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -227,13 +276,19 @@ import { ref, onMounted, onUnmounted, computed, watch } from "vue";
|
|
|
import { useRoute, useRouter } from "vue-router";
|
|
|
import { searchVideoByTags, getVideoDetail } from "@/services/api";
|
|
|
import VideoProcessor from "@/components/VideoProcessor.vue";
|
|
|
+import { useUserStore } from "@/store/user";
|
|
|
+import { VipLevel, canWatchFullVideo, getTrialDuration } from "@/types/vip";
|
|
|
|
|
|
// 路由相关
|
|
|
const route = useRoute();
|
|
|
const router = useRouter();
|
|
|
|
|
|
+// 用户状态
|
|
|
+const userStore = useUserStore();
|
|
|
+
|
|
|
// 视频播放器引用(现在由 VideoProcessor 组件管理)
|
|
|
const videoPlayer = ref<HTMLVideoElement>();
|
|
|
+const videoProcessorRef = ref<any>();
|
|
|
|
|
|
// 视频信息
|
|
|
const videoInfo = ref<any>({
|
|
|
@@ -253,6 +308,8 @@ const relatedVideos = ref<any[]>([]);
|
|
|
|
|
|
// 状态管理
|
|
|
const showShareModal = ref(false);
|
|
|
+const showPurchaseModal = ref(false);
|
|
|
+const isTrialMode = ref(false);
|
|
|
|
|
|
// 生成设备标识
|
|
|
const generateMacAddress = (): string => {
|
|
|
@@ -268,6 +325,11 @@ const generateMacAddress = (): string => {
|
|
|
|
|
|
const device = generateMacAddress();
|
|
|
|
|
|
+// 计算属性
|
|
|
+const currentVipLevel = computed(() => userStore.getVipLevel());
|
|
|
+const isGuestOrFree = computed(() => !canWatchFullVideo(currentVipLevel.value));
|
|
|
+const trialDuration = computed(() => getTrialDuration());
|
|
|
+
|
|
|
// VideoProcessor 事件处理
|
|
|
const onCoverLoaded = (url: string) => {
|
|
|
// 封面加载完成
|
|
|
@@ -285,6 +347,42 @@ const onVideoProcessorRetry = () => {
|
|
|
// VideoProcessor 重试
|
|
|
};
|
|
|
|
|
|
+const onVideoPlay = () => {
|
|
|
+ // 如果是guest或free用户且还没有开始试看,则开始试看
|
|
|
+ if (isGuestOrFree.value && !isTrialMode.value) {
|
|
|
+ startTrial();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 监听视频播放进度
|
|
|
+const onVideoTimeUpdate = () => {
|
|
|
+ if (isGuestOrFree.value && isTrialMode.value) {
|
|
|
+ const video = document.querySelector("video");
|
|
|
+ if (video && video.currentTime > trialDuration.value) {
|
|
|
+ // 超过试看时间,停止播放并显示购买弹窗
|
|
|
+ video.pause();
|
|
|
+ // 将视频时间重置到试看结束时间
|
|
|
+ video.currentTime = trialDuration.value;
|
|
|
+ // 停止HLS加载
|
|
|
+ if (videoProcessorRef.value) {
|
|
|
+ videoProcessorRef.value.stopHlsLoading();
|
|
|
+ }
|
|
|
+ showPurchaseModal.value = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 监听视频时间变化(包括拖拽进度条)
|
|
|
+const onVideoSeeking = () => {
|
|
|
+ if (isGuestOrFree.value && isTrialMode.value) {
|
|
|
+ const video = document.querySelector("video");
|
|
|
+ if (video && video.currentTime > trialDuration.value) {
|
|
|
+ // 如果拖拽到超过试看时间,强制回到试看结束时间
|
|
|
+ video.currentTime = trialDuration.value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
// 格式化时长
|
|
|
const formatDuration = (duration: string | number): string => {
|
|
|
const seconds = parseInt(String(duration));
|
|
|
@@ -329,15 +427,78 @@ const toggleShare = async () => {
|
|
|
|
|
|
// 播放视频
|
|
|
const playVideo = (video: any) => {
|
|
|
- router.push({
|
|
|
- name: "VideoPlayer",
|
|
|
- params: { id: video.id },
|
|
|
- });
|
|
|
+ const vipLevel = userStore.getVipLevel();
|
|
|
+
|
|
|
+ if (vipLevel === VipLevel.GUEST || vipLevel === VipLevel.FREE) {
|
|
|
+ // guest和free用户通过URL参数传递cover和m3u8,同时包含视频ID
|
|
|
+ router.push({
|
|
|
+ name: "VideoPlayer",
|
|
|
+ params: { id: video.id },
|
|
|
+ query: {
|
|
|
+ cover: video.cover,
|
|
|
+ m3u8: video.m3u8,
|
|
|
+ name: video.name,
|
|
|
+ duration: video.duration,
|
|
|
+ view: video.view,
|
|
|
+ like: video.like,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 其他VIP用户正常调用详情接口
|
|
|
+ router.push({
|
|
|
+ name: "VideoPlayer",
|
|
|
+ params: { id: video.id },
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 开始试看
|
|
|
+const startTrial = () => {
|
|
|
+ isTrialMode.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+// 购买会员
|
|
|
+const purchaseMembership = () => {
|
|
|
+ // TODO: 实现购买会员逻辑
|
|
|
+ console.log("购买会员");
|
|
|
+ showPurchaseModal.value = false;
|
|
|
+};
|
|
|
+
|
|
|
+// 单独购买本片
|
|
|
+const purchaseVideo = () => {
|
|
|
+ // TODO: 实现单独购买逻辑
|
|
|
+ console.log("单独购买本片");
|
|
|
+ showPurchaseModal.value = false;
|
|
|
};
|
|
|
|
|
|
// 加载视频信息
|
|
|
const loadVideoInfo = async () => {
|
|
|
- // 优先从路由参数获取ID
|
|
|
+ const vipLevel = userStore.getVipLevel();
|
|
|
+
|
|
|
+ // 如果是guest或free用户,从query参数获取视频信息
|
|
|
+ if (vipLevel === VipLevel.GUEST || vipLevel === VipLevel.FREE) {
|
|
|
+ const { cover, m3u8, name, duration, view, like } = route.query;
|
|
|
+
|
|
|
+ if (cover && m3u8) {
|
|
|
+ videoInfo.value = {
|
|
|
+ id: "trial",
|
|
|
+ name: name || "试看视频",
|
|
|
+ cover: cover as string,
|
|
|
+ m3u8: m3u8 as string,
|
|
|
+ duration: parseInt(duration as string) || 0,
|
|
|
+ view: parseInt(view as string) || 0,
|
|
|
+ like: parseInt(like as string) || 0,
|
|
|
+ time: 0,
|
|
|
+ taginfo: [],
|
|
|
+ };
|
|
|
+
|
|
|
+ // 设置页面标题
|
|
|
+ document.title = `${videoInfo.value.name} - 试看`;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 其他情况,从路由参数获取ID
|
|
|
const videoId = route.params.id || route.query.id;
|
|
|
|
|
|
if (videoId) {
|
|
|
@@ -474,7 +635,9 @@ onMounted(async () => {
|
|
|
}
|
|
|
});
|
|
|
|
|
|
-// 组件卸载时的清理工作已移至 VideoProcessor 组件
|
|
|
+onUnmounted(() => {
|
|
|
+ // 组件卸载时的清理工作
|
|
|
+});
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|