index.vue 7.3 KB
<template>
  <view class="video-player-page">
    <!-- 导航头 -->
    <NavHeader title="视频播放" :transparent="false" />

    <!-- 视频播放器 -->
    <view class="video-container">
      <video
        v-if="videoUrl"
        :src="videoUrl"
        :poster="poster"
        :autoplay="false"
        :initial-time="0"
        :controls="true"
        :loop="false"
        :muted="false"
        :show-center-play-btn="true"
        :show-play-btn="true"
        :enable-progress-gesture="true"
        :object-fit="'contain'"
        :show-mute-btn="true"
        :show-fullscreen-btn="true"
        :show-screen-lock-button="true"
        :play-btn-position="'center'"
        class="video-player"
        @loadedmetadata="onLoadedMetadata"
        @canplay="onCanPlay"
        @play="onPlay"
        @pause="onPause"
        @ended="onEnded"
        @error="onError"
        @timeupdate="onTimeUpdate"
        @fullscreenchange="onFullscreenChange"
        @waiting="onWaiting"
        @playing="onPlaying"
        id="videoPlayer"
      />

      <!-- 加载状态(已禁用,使用视频播放器自带的加载指示器)-->
      <!-- <view v-if="loading" class="loading-overlay">
        <view class="loading-content">
          <view class="loading-spinner"></view>
          <text class="loading-text">加载中...</text>
        </view>
      </view> -->

      <!-- 错误提示 -->
      <view v-if="error" class="error-overlay">
        <view class="error-content">
          <text class="error-icon">⚠️</text>
          <text class="error-message">{{ errorMessage }}</text>
          <view class="retry-button" @tap="retryLoad">
            <text class="retry-text">重试</text>
          </view>
        </view>
      </view>
    </view>

    <!-- 视频信息 -->
    <!-- <view v-if="videoTitle && !error" class="video-info">
      <text class="video-title">{{ videoTitle }}</text>
      <text class="video-tips">温馨提示:您可以点击右下角全屏按钮观看</text>
    </view> -->
  </view>
</template>

<script setup>
import { ref } from 'vue'
import Taro, { useLoad, useDidHide } from '@tarojs/taro'
import { showToast } from '@tarojs/taro'
import NavHeader from '@/components/NavHeader.vue'

/**
 * 视频播放页面
 *
 * @description 支持播放小程序兼容的视频格式(MP4、M4V、MOV)
 * @author Claude Code
 * @date 2026-02-04
 */

// 响应式状态
const videoUrl = ref('')
const videoTitle = ref('')
const poster = ref('')
const loading = ref(false)
const error = ref(false)
const errorMessage = ref('')

/**
 * 页面加载时获取视频URL和标题
 */
useLoad((options) => {
  console.log('视频播放页面参数:', options)

  // 获取视频URL
  if (options.url) {
    try {
      videoUrl.value = decodeURIComponent(options.url)
    } catch (err) {
      console.error('URL解码失败:', err)
      showError('视频地址解析失败')
    }
  } else {
    showError('缺少视频地址')
  }

  // 获取视频标题
  if (options.title) {
    try {
      videoTitle.value = decodeURIComponent(options.title)
    } catch (err) {
      console.error('标题解码失败:', err)
    }
  }
})

/**
 * 页面隐藏时暂停视频播放
 */
useDidHide(() => {
  // 暂停视频播放
  const videoContext = Taro.createVideoContext('videoPlayer')
  if (videoContext) {
    videoContext.pause()
  }
})

/**
 * 视频元数据加载完成
 */
const onLoadedMetadata = () => {
  console.log('视频元数据加载完成')
  // 元数据加载完成,但还需要等待 canplay 事件才能播放
}

/**
 * 视频可以播放
 */
const onCanPlay = () => {
  console.log('视频可以播放')
  loading.value = false
  error.value = false
}

/**
 * 视频开始播放
 */
const onPlay = () => {
  console.log('视频开始播放')
  loading.value = false
  error.value = false
}

/**
 * 视频暂停事件
 */
const onPause = () => {
  console.log('视频暂停')
}

/**
 * 视频等待数据
 */
const onWaiting = () => {
  console.log('视频等待数据')
  loading.value = true
}

/**
 * 视频播放中
 */
const onPlaying = () => {
  console.log('视频播放中')
  loading.value = false
}

/**
 * 视频播放结束事件
 */
const onEnded = () => {
  console.log('视频播放结束')
  showToast({
    title: '播放结束',
    icon: 'success',
    duration: 1500
  })
}

/**
 * 视频加载错误事件
 */
const onError = (e) => {
  console.error('视频加载错误:', e)
  showError('视频加载失败,请检查网络或视频格式')
}

/**
 * 视频播放进度更新事件
 */
const onTimeUpdate = (e) => {
  // 可以在这里记录播放进度
  const { currentTime, duration } = e.detail
  console.log(`播放进度: ${currentTime}/${duration}`)
}

/**
 * 全屏切换事件
 */
const onFullscreenChange = (e) => {
  const { fullScreen, direction } = e.detail
  console.log(`全屏状态: ${fullScreen}, 方向: ${direction}`)
}

/**
 * 显示错误信息
 */
const showError = (message) => {
  loading.value = false
  error.value = true
  errorMessage.value = message
}

/**
 * 重新加载视频
 */
const retryLoad = () => {
  error.value = false
  loading.value = true
  errorMessage.value = ''

  // 重新设置视频URL会触发重新加载
  const originalUrl = videoUrl.value
  videoUrl.value = ''
  setTimeout(() => {
    videoUrl.value = originalUrl
  }, 100)
}
</script>

<style lang="less">
.video-player-page {
  width: 100%;
  height: 100vh;
  background-color: #000;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.video-container {
  position: relative;
  flex: 1;
  width: 100%;
  background-color: #000;
  overflow: hidden;
}

.video-player {
  width: 100%;
  height: 100%;
}

.loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, 0.7);
  z-index: 10;
}

.loading-content {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.loading-spinner {
  width: 60rpx;
  height: 60rpx;
  border: 4rpx solid rgba(255, 255, 255, 0.3);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin-bottom: 20rpx;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.loading-text {
  font-size: 28rpx;
  color: #fff;
}

.error-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, 0.9);
  z-index: 20;
}

.error-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 40rpx;
  background-color: #1a1a1a;
  border-radius: 16rpx;
  max-width: 80%;
}

.error-icon {
  font-size: 80rpx;
  margin-bottom: 20rpx;
}

.error-message {
  font-size: 28rpx;
  color: #fff;
  margin-bottom: 30rpx;
  text-align: center;
}

.retry-button {
  padding: 16rpx 40rpx;
  background-color: #2563EB;
  border-radius: 8rpx;
}

.retry-text {
  font-size: 28rpx;
  color: #fff;
}

.video-info {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 32rpx;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);
  z-index: 5;
}

.video-title {
  display: block;
  font-size: 32rpx;
  font-weight: bold;
  color: #fff;
  margin-bottom: 16rpx;
  line-height: 1.4;
}

.video-tips {
  display: block;
  font-size: 24rpx;
  color: rgba(255, 255, 255, 0.7);
  line-height: 1.4;
}
</style>