UniApp短视频应用开发全流程 | 多端适配与高性能渲染实战

2025-08-14 0 782

一、项目架构设计

本教程将基于UniApp构建一个适配微信小程序、H5和App的短视频应用,实现从视频采集到播放的全流程解决方案。

技术架构:

  • 核心框架:UniApp 3.0 + Vue3
  • 视频处理:腾讯云点播SDK
  • 状态管理:Pinia 2.0
  • UI组件:uView UI 3.0
  • 性能监控:自定义性能统计SDK

核心功能模块:

  1. 视频流瀑布布局
  2. 手势滑动切换
  3. 视频预加载机制
  4. 多端录制上传
  5. 播放器性能优化

二、项目初始化与配置

1. 项目创建与扩展安装

# 通过Vue CLI创建项目
vue create -p dcloudio/uni-preset-vue short-video-app

# 安装必要依赖
cd short-video-app
npm install uview-ui @dcloudio/uni-ui pinia

# 配置vue.config.js
module.exports = {
  transpileDependencies: ['uview-ui'],
  configureWebpack: {
    optimization: {
      splitChunks: {
        chunks: 'all',
        maxSize: 500 * 1024
      }
    }
  }
}

2. 多端兼容目录结构

src/
├── common/
│   ├── libs/         # 多端通用库
│   ├── utils/        # 工具函数
│   └── styles/       # 全局样式
├── components/       # 通用组件
├── pages/
│   ├── index/        # 首页
│   ├── record/       # 录制页
│   └── profile/      # 个人页
├── static/
│   ├── videos/       # 本地测试视频
│   └── icons/        # 图标资源
├── store/            # Pinia状态
├── App.vue           # 应用入口
└── main.js           # 项目入口

三、核心功能实现

1. 视频流组件开发

<template>
  <scroll-view 
    class="video-feed"
    scroll-y
    @scrolltolower="loadMore"
    :scroll-with-animation="true">
    
    <video 
      v-for="(item, index) in videoList"
      :key="item.id"
      :id="'video-'+index"
      :src="item.url"
      :autoplay="currentIndex === index"
      :controls="false"
      :show-center-play-btn="false"
      :enable-progress-gesture="false"
      @play="handlePlay(index)"
      @error="handleError(index)"
      class="video-item">
    </video>
    
    <uni-load-more :status="loadingStatus" />
  </scroll-view>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { fetchVideoList } from '@/api/video'

const videoList = ref([])
const loadingStatus = ref('more')
const currentIndex = ref(0)

const loadData = async () => {
  if (loadingStatus.value === 'loading') return
  
  loadingStatus.value = 'loading'
  try {
    const res = await fetchVideoList({
      page: Math.ceil(videoList.value.length / 10) + 1,
      size: 10
    })
    
    videoList.value = [...videoList.value, ...res.list]
    loadingStatus.value = res.hasMore ? 'more' : 'noMore'
    
    // 预加载下个视频
    preloadNextVideo()
  } catch (e) {
    loadingStatus.value = 'error'
  }
}

const preloadNextVideo = () => {
  const nextIndex = currentIndex.value + 1
  if (nextIndex  videoContext.pause())
  }
}

const handlePlay = (index) => {
  // 暂停其他视频
  videoList.value.forEach((_, i) => {
    if (i !== index) {
      const videoContext = uni.createVideoContext(`video-${i}`)
      videoContext.pause()
    }
  })
  
  currentIndex.value = index
  preloadNextVideo()
}

onMounted(() => {
  loadData()
})
</script>

2. 多端录制实现

// pages/record/record.vue
<template>
  <view class="record-page">
    <camera 
      v-if="showCamera"
      device-position="front"
      flash="off"
      @error="cameraError"
      class="camera">
    </camera>
    
    <view class="controls">
      <button @click="toggleCamera">切换摄像头</button>
      <button @click="startRecord" :disabled="isRecording">开始录制</button>
      <button @click="stopRecord" :disabled="!isRecording">停止录制</button>
    </view>
  </view>
</template>

<script setup>
import { ref } from 'vue'
import { uploadVideo } from '@/api/video'

const showCamera = ref(true)
const isRecording = ref(false)
const cameraContext = ref(null)

// 初始化相机上下文
onMounted(() => {
  cameraContext.value = uni.createCameraContext()
})

const toggleCamera = () => {
  showCamera.value = false
  nextTick(() => {
    showCamera.value = true
  })
}

const startRecord = () => {
  isRecording.value = true
  cameraContext.value.startRecord({
    success: () => console.log('开始录制'),
    fail: (err) => console.error('录制失败:', err)
  })
}

const stopRecord = async () => {
  isRecording.value = false
  cameraContext.value.stopRecord({
    success: async (res) => {
      const { tempVideoPath } = res
      try {
        await uploadVideo(tempVideoPath)
        uni.showToast({ title: '上传成功', icon: 'success' })
      } catch (e) {
        uni.showToast({ title: '上传失败', icon: 'error' })
      }
    }
  })
}
</script>

四、性能优化策略

1. 视频预加载方案

// utils/videoPreload.js
export const preloadVideos = (videos) => {
  // 小程序端使用后台播放器预加载
  // #ifdef MP-WEIXIN
  const backgroundAudioManager = uni.getBackgroundAudioManager()
  videos.forEach(video => {
    backgroundAudioManager.src = video.url
    backgroundAudioManager.pause()
  })
  // #endif
  
  // H5端使用video元素预加载
  // #ifdef H5
  videos.forEach(video => {
    const videoEl = document.createElement('video')
    videoEl.src = video.url
    videoEl.preload = 'auto'
    document.body.appendChild(videoEl)
  })
  // #endif
  
  // App端使用原生预加载
  // #ifdef APP-PLUS
  const videoPlayer = plus.video.createVideoPlayer('preloader', {
    top: '-1000px',
    height: '1px',
    width: '1px'
  })
  videos.forEach(video => {
    videoPlayer.load(video.url)
    videoPlayer.pause()
  })
  // #endif
}

// 在页面中使用
watch(videoList, (newVal) => {
  const nextVideos = newVal.slice(currentIndex.value, currentIndex.value + 3)
  preloadVideos(nextVideos)
})

2. 播放器性能优化

// 使用uniapp的video组件优化属性
<video
  :src="currentVideo.url"
  :autoplay="true"
  :controls="false"
  :show-center-play-btn="false"
  :enable-progress-gesture="false"
  :enable-play-gesture="true"
  :vslide-gesture="true"
  :vslide-gesture-in-fullscreen="true"
  :picture-in-picture-mode="['push', 'pop']"
  :danmu-list="danmuList"
  @play="onPlay"
  @pause="onPause"
  @ended="onEnded"
  @error="onError">
</video>

// 播放器事件处理
const onPlay = () => {
  // 暂停所有其他视频
  videoList.value.forEach((_, index) => {
    if (index !== currentIndex.value) {
      const videoContext = uni.createVideoContext(`video-${index}`)
      videoContext.pause()
    }
  })
  
  // 上报播放数据
  reportPlayStart(currentVideo.value.id)
  
  // 预加载下一个视频
  preloadNextVideo()
}

// 使用worker处理弹幕数据
const initDanmuWorker = () => {
  const worker = uni.createWorker('workers/danmu.js')
  
  worker.onMessage((res) => {
    danmuList.value = res.data
  })
  
  worker.postMessage({
    action: 'init',
    videoId: currentVideo.value.id
  })
}

五、多端兼容方案

1. 平台差异化代码处理

// 录制功能兼容多端
const startRecord = () => {
  // #ifdef MP-WEIXIN
  wx.startRecord({
    success: (res) => {
      handleRecordSuccess(res.tempFilePath)
    }
  })
  // #endif
  
  // #ifdef APP-PLUS
  plus.camera.getCamera().captureVideo(
    (res) => handleRecordSuccess(res),
    (err) => console.error(err)
  )
  // #endif
  
  // #ifdef H5
  if (navigator.mediaDevices) {
    navigator.mediaDevices.getUserMedia({ video: true })
      .then(stream => {
        mediaRecorder.value = new MediaRecorder(stream)
        mediaRecorder.value.start()
      })
  } else {
    uni.showToast({ title: '浏览器不支持录制', icon: 'none' })
  }
  // #endif
}

// 上传功能兼容处理
const uploadVideo = async (filePath) => {
  // #ifdef MP-WEIXIN || APP-PLUS
  const [uploadRes] = await uni.uploadFile({
    url: '/api/upload',
    filePath,
    name: 'video'
  })
  return JSON.parse(uploadRes.data)
  // #endif
  
  // #ifdef H5
  const formData = new FormData()
  formData.append('video', filePath)
  
  const res = await fetch('/api/upload', {
    method: 'POST',
    body: formData
  })
  return await res.json()
  // #endif
}

2. 统一API封装

// api/video.js
import { request } from '@/utils/request'

export const fetchVideoList = (params) => {
  return request({
    url: '/video/list',
    method: 'GET',
    params
  })
}

export const uploadVideo = (filePath) => {
  return new Promise((resolve, reject) => {
    // #ifdef MP-WEIXIN || APP-PLUS
    uni.uploadFile({
      url: '/api/upload',
      filePath,
      name: 'video',
      success: (res) => {
        resolve(JSON.parse(res.data))
      },
      fail: reject
    })
    // #endif
    
    // #ifdef H5
    const input = document.createElement('input')
    input.type = 'file'
    input.accept = 'video/*'
    
    input.onchange = (e) => {
      const file = e.target.files[0]
      const formData = new FormData()
      formData.append('video', file)
      
      fetch('/api/upload', {
        method: 'POST',
        body: formData
      })
      .then(res => res.json())
      .then(resolve)
      .catch(reject)
    }
    
    input.click()
    // #endif
  })
}

// utils/request.js
export const request = (options) => {
  return new Promise((resolve, reject) => {
    // 统一处理请求
    uni.request({
      ...options,
      success: (res) => {
        if (res.statusCode === 200) {
          resolve(res.data)
        } else {
          reject(res)
        }
      },
      fail: (err) => {
        reject(err)
      }
    })
  })
}

六、部署与发布

1. 多端发布配置

// manifest.json 配置示例
{
  "name": "短视频应用",
  "appid": "__UNI__XXXXXX",
  "description": "跨平台短视频应用",
  
  /* 小程序特有配置 */
  "mp-weixin": {
    "appid": "wxXXXXXXXXXXXXXX",
    "usingComponents": true,
    "permission": {
      "scope.userLocation": {
        "desc": "需要获取您的位置信息用于附近视频展示"
      }
    }
  },
  
  /* App特有配置 */
  "app-plus": {
    "distribute": {
      "android": {
        "permissions": [
          "<uses-permission android:name="android.permission.CAMERA"/>",
          "<uses-permission android:name="android.permission.RECORD_AUDIO"/>"
        ]
      },
      "ios": {
        "UIRequiresFullScreen": true
      }
    }
  },
  
  /* H5配置 */
  "h5": {
    "router": {
      "mode": "history"
    },
    "template": "template.h5.html"
  }
}

七、总结与扩展

本教程构建了一个完整的短视频应用:

  1. 实现了视频流核心功能
  2. 优化了多端录制体验
  3. 完善了性能优化方案
  4. 解决了多端兼容问题

扩展方向:

  • 视频编辑功能集成
  • 直播功能扩展
  • AI内容审核
  • WebAssembly视频处理

完整项目代码已开源:https://github.com/example/uniapp-short-video

UniApp短视频应用开发全流程 | 多端适配与高性能渲染实战
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 uniapp UniApp短视频应用开发全流程 | 多端适配与高性能渲染实战 https://www.taomawang.com/web/uniapp/829.html

下一篇:

已经没有下一篇了!

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务