Skip to content

uni-app 媒體元件指南

本指南詳細介紹 uni-app 的媒體元件,包括圖片、音訊、影片、相機等多媒體功能元件的使用方法。

目錄


image 圖片元件

image 元件用於展示圖片,支援 JPG、PNG、SVG、WEBP、GIF 等格式。

主要屬性

屬性名類型預設值說明
srcString-圖片資源地址
modeStringscaleToFill圖片裁剪、縮放的模式
lazy-loadBooleanfalse圖片懶載入
fade-showBooleantrue圖片顯示動畫效果
webpBooleanfalse是否解析 webP 格式
show-menu-by-longpressBooleanfalse長按顯示識別選單

mode 屬性值

說明
scaleToFill不保持縱橫比縮放圖片,使圖片完全適應
aspectFit保持縱橫比縮放,長邊完全顯示
aspectFill保持縱橫比縮放,短邊完全顯示
widthFix寬度不變,高度自動變化
heightFix高度不變,寬度自動變化
top不縮放,只顯示頂部區域
bottom不縮放,只顯示底部區域
center不縮放,只顯示中間區域
left不縮放,只顯示左邊區域
right不縮放,只顯示右邊區域

使用範例

基礎用法

vue
<template>
  <view class="container">
    <!-- 基本圖片顯示 -->
    <image 
      src="/static/logo.png" 
      mode="aspectFit"
      class="logo"
    />
    
    <!-- 網路圖片 -->
    <image 
      src="https://example.com/image.jpg" 
      mode="widthFix"
      lazy-load
      @load="onImageLoad"
      @error="onImageError"
    />
  </view>
</template>

<script>
export default {
  methods: {
    onImageLoad(e) {
      console.log('圖片載入完成', e.detail)
    },
    onImageError(e) {
      console.error('圖片載入失敗', e.detail.errMsg)
    }
  }
}
</script>

<style>
.container {
  padding: 20px;
}
.logo {
  width: 100px;
  height: 100px;
  border-radius: 8px;
}
</style>

不同縮放模式對比

vue
<template>
  <view class="container">
    <view class="mode-list">
      <view 
        class="mode-item" 
        v-for="mode in modes" 
        :key="mode"
      >
        <image 
          :src="imageUrl" 
          :mode="mode" 
          class="demo-image"
        />
        <text class="mode-name">{{ mode }}</text>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      imageUrl: 'https://example.com/demo.jpg',
      modes: [
        'scaleToFill', 'aspectFit', 'aspectFill', 
        'widthFix', 'heightFix', 'center'
      ]
    }
  }
}
</script>

<style>
.mode-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.mode-item {
  width: calc(50% - 5px);
  text-align: center;
}
.demo-image {
  width: 100%;
  height: 120px;
  border: 1px solid #eee;
  border-radius: 4px;
}
.mode-name {
  display: block;
  font-size: 12px;
  color: #666;
  margin-top: 5px;
}
</style>

video 影片元件

video 元件用於播放影片,支援多種影片格式和豐富的播放控制功能。

主要屬性

屬性名類型預設值說明
srcString-影片資源地址
autoplayBooleanfalse是否自動播放
loopBooleanfalse是否循環播放
mutedBooleanfalse是否靜音播放
controlsBooleantrue是否顯示預設播放控制項
posterString-影片封面圖片地址
initial-timeNumber0指定影片初始播放位置(秒)
durationNumber-指定影片時長(秒)
object-fitStringcontain影片填充模式
show-fullscreen-btnBooleantrue是否顯示全螢幕按鈕
show-play-btnBooleantrue是否顯示播放按鈕
show-center-play-btnBooleantrue是否顯示影片中間的播放按鈕
enable-progress-gestureBooleantrue是否開啟控制進度的手勢
danmu-listArray-彈幕列表
danmu-btnBooleanfalse是否顯示彈幕按鈕
enable-danmuBooleanfalse是否展示彈幕

使用範例

基礎影片播放

vue
<template>
  <view class="container">
    <video
      id="myVideo"
      src="https://example.com/video.mp4"
      controls
      poster="https://example.com/poster.jpg"
      object-fit="cover"
      @play="onVideoPlay"
      @pause="onVideoPause"
      @ended="onVideoEnded"
      @timeupdate="onTimeUpdate"
      @error="onVideoError"
      class="video-player"
    />
    
    <view class="video-controls">
      <button @click="playVideo">播放</button>
      <button @click="pauseVideo">暫停</button>
      <button @click="seekTo30">跳轉到30秒</button>
      <button @click="toggleMute">
        {{ isMuted ? '取消靜音' : '靜音' }}
      </button>
    </view>
    
    <view class="video-info">
      <text>目前時間: {{ currentTime }}s</text>
      <text>總時長: {{ totalDuration }}s</text>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      videoContext: null,
      isMuted: false,
      currentTime: 0,
      totalDuration: 0
    }
  },
  onReady() {
    this.videoContext = uni.createVideoContext('myVideo', this)
  },
  methods: {
    onVideoPlay() {
      console.log('影片開始播放')
    },
    onVideoPause() {
      console.log('影片暫停')
    },
    onVideoEnded() {
      console.log('影片播放結束')
    },
    onTimeUpdate(e) {
      this.currentTime = Math.floor(e.detail.currentTime)
      this.totalDuration = Math.floor(e.detail.duration)
    },
    onVideoError(e) {
      console.error('影片播放錯誤', e.detail)
    },
    playVideo() {
      this.videoContext.play()
    },
    pauseVideo() {
      this.videoContext.pause()
    },
    seekTo30() {
      this.videoContext.seek(30)
    },
    toggleMute() {
      this.isMuted = !this.isMuted
      // 注意:需要重新設定video元件的muted屬性
    }
  }
}
</script>

<style>
.container {
  padding: 20px;
}
.video-player {
  width: 100%;
  height: 200px;
  border-radius: 8px;
}
.video-controls {
  display: flex;
  gap: 10px;
  margin: 15px 0;
  flex-wrap: wrap;
}
.video-controls button {
  flex: 1;
  min-width: 80px;
  padding: 8px;
  background-color: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
}
.video-info {
  display: flex;
  justify-content: space-between;
  font-size: 14px;
  color: #666;
}
</style>

帶彈幕的影片播放

vue
<template>
  <view class="container">
    <video
      id="danmuVideo"
      src="https://example.com/video.mp4"
      controls
      danmu-btn
      enable-danmu
      :danmu-list="danmuList"
      class="video-player"
    />
    
    <view class="danmu-input">
      <input 
        v-model="danmuText" 
        placeholder="輸入彈幕內容"
        class="danmu-input-field"
      />
      <button @click="sendDanmu" class="send-btn">發送</button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      videoContext: null,
      danmuText: '',
      danmuList: [
        { text: '第1秒彈幕', color: '#ff0000', time: 1 },
        { text: '第3秒彈幕', color: '#00ff00', time: 3 },
        { text: '第5秒彈幕', color: '#0000ff', time: 5 }
      ]
    }
  },
  onReady() {
    this.videoContext = uni.createVideoContext('danmuVideo', this)
  },
  methods: {
    sendDanmu() {
      if (!this.danmuText.trim()) return
      
      this.videoContext.sendDanmu({
        text: this.danmuText,
        color: this.getRandomColor()
      })
      
      this.danmuText = ''
    },
    getRandomColor() {
      const colors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff']
      return colors[Math.floor(Math.random() * colors.length)]
    }
  }
}
</script>

<style>
.danmu-input {
  display: flex;
  gap: 10px;
  margin-top: 15px;
}
.danmu-input-field {
  flex: 1;
  height: 40px;
  padding: 0 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
}
.send-btn {
  width: 80px;
  background-color: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
}
</style>

audio 音訊元件

audio 元件用於播放音訊檔案,支援多種音訊格式。

主要屬性

屬性名類型預設值說明
idString-音訊元件唯一識別符
srcString-音訊資源地址
loopBooleanfalse是否循環播放
controlsBooleanfalse是否顯示預設控制項
posterString-音訊封面圖片地址
nameString未知音訊音訊名稱
authorString未知作者作者名稱
autoplayBooleanfalse是否自動播放

使用範例

基礎音訊播放

vue
<template>
  <view class="container">
    <audio
      id="myAudio"
      src="https://example.com/audio.mp3"
      controls
      poster="https://example.com/cover.jpg"
      name="範例音訊"
      author="範例作者"
      @play="onAudioPlay"
      @pause="onAudioPause"
      @ended="onAudioEnded"
      @timeupdate="onAudioTimeUpdate"
      @error="onAudioError"
    />
  </view>
</template>

<script>
export default {
  data() {
    return {
      audioContext: null
    }
  },
  onReady() {
    this.audioContext = uni.createAudioContext('myAudio', this)
  },
  methods: {
    onAudioPlay() {
      console.log('音訊開始播放')
    },
    onAudioPause() {
      console.log('音訊暫停')
    },
    onAudioEnded() {
      console.log('音訊播放結束')
    },
    onAudioTimeUpdate(e) {
      console.log('播放進度', e.detail.currentTime, e.detail.duration)
    },
    onAudioError(e) {
      console.error('音訊播放錯誤', e.detail)
    }
  }
}
</script>

自訂音訊播放器

vue
<template>
  <view class="container">
    <!-- 隱藏的audio元件 -->
    <audio
      id="customAudio"
      :src="currentAudio.src"
      @play="onPlay"
      @pause="onPause"
      @ended="onEnded"
      @timeupdate="onTimeUpdate"
      style="display: none;"
    />
    
    <!-- 自訂播放器介面 -->
    <view class="custom-player">
      <view class="audio-info">
        <image 
          :src="currentAudio.poster" 
          class="audio-cover"
          mode="aspectFill"
        />
        <view class="audio-details">
          <text class="audio-name">{{ currentAudio.name }}</text>
          <text class="audio-author">{{ currentAudio.author }}</text>
        </view>
      </view>
      
      <view class="progress-section">
        <text class="time-text">{{ formatTime(currentTime) }}</text>
        <slider
          :value="progressValue"
          :max="100"
          @change="onProgressChange"
          class="progress-slider"
        />
        <text class="time-text">{{ formatTime(duration) }}</text>
      </view>
      
      <view class="control-buttons">
        <text class="control-btn" @click="prevSong">⏮</text>
        <text 
          class="control-btn play-btn" 
          @click="togglePlay"
        >
          {{ isPlaying ? '⏸' : '▶' }}
        </text>
        <text class="control-btn" @click="nextSong">⏭</text>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      audioContext: null,
      isPlaying: false,
      currentTime: 0,
      duration: 0,
      progressValue: 0,
      currentIndex: 0,
      playlist: [
        {
          src: 'https://example.com/song1.mp3',
          name: '歌曲1',
          author: '歌手1',
          poster: 'https://example.com/cover1.jpg'
        },
        {
          src: 'https://example.com/song2.mp3',
          name: '歌曲2',
          author: '歌手2',
          poster: 'https://example.com/cover2.jpg'
        }
      ]
    }
  },
  computed: {
    currentAudio() {
      return this.playlist[this.currentIndex] || {}
    }
  },
  onReady() {
    this.audioContext = uni.createAudioContext('customAudio', this)
  },
  methods: {
    onPlay() {
      this.isPlaying = true
    },
    onPause() {
      this.isPlaying = false
    },
    onEnded() {
      this.isPlaying = false
      this.nextSong()
    },
    onTimeUpdate(e) {
      this.currentTime = e.detail.currentTime
      this.duration = e.detail.duration
      
      if (this.duration > 0) {
        this.progressValue = (this.currentTime / this.duration) * 100
      }
    },
    togglePlay() {
      if (this.isPlaying) {
        this.audioContext.pause()
      } else {
        this.audioContext.play()
      }
    },
    prevSong() {
      this.currentIndex = (this.currentIndex - 1 + this.playlist.length) % this.playlist.length
      this.audioContext.play()
    },
    nextSong() {
      this.currentIndex = (this.currentIndex + 1) % this.playlist.length
      this.audioContext.play()
    },
    onProgressChange(e) {
      const value = e.detail.value
      const seekTime = (value / 100) * this.duration
      this.audioContext.seek(seekTime)
    },
    formatTime(time) {
      const minutes = Math.floor(time / 60)
      const seconds = Math.floor(time % 60)
      return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
    }
  }
}
</script>

<style>
.custom-player {
  background: #fff;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.audio-info {
  display: flex;
  align-items: center;
  margin-bottom: 20px;
}
.audio-cover {
  width: 60px;
  height: 60px;
  border-radius: 8px;
  margin-right: 15px;
}
.audio-details {
  flex: 1;
}
.audio-name {
  display: block;
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 5px;
}
.audio-author {
  display: block;
  font-size: 14px;
  color: #666;
}
.progress-section {
  display: flex;
  align-items: center;
  margin-bottom: 20px;
}
.time-text {
  font-size: 12px;
  color: #999;
  width: 40px;
}
.progress-slider {
  flex: 1;
  margin: 0 10px;
}
.control-buttons {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 30px;
}
.control-btn {
  font-size: 24px;
  color: #42b983;
}
.play-btn {
  font-size: 32px;
}
</style>

camera 相機元件

camera 元件用於呼叫裝置攝影機進行拍照或錄影。

主要屬性

屬性名類型預設值說明
modeStringnormal模式:normal(拍照/錄影)、scanCode(掃碼)
resolutionStringmedium解析度:low、medium、high
device-positionStringback攝影機:front(前置)、back(後置)
flashStringauto閃光燈:auto、on、off
frame-sizeStringmedium期望的相機幀資料尺寸

使用範例

基礎相機功能

vue
<template>
  <view class="container">
    <camera
      class="camera"
      :device-position="devicePosition"
      :flash="flash"
      @error="onCameraError"
      @initdone="onCameraInit"
    />
    
    <view class="camera-controls">
      <button @click="takePhoto" class="control-btn">拍照</button>
      <button @click="startRecord" class="control-btn">開始錄影</button>
      <button @click="stopRecord" class="control-btn">停止錄影</button>
      <button @click="switchCamera" class="control-btn">切換攝影機</button>
      <button @click="toggleFlash" class="control-btn">
        閃光燈: {{ flash }}
      </button>
    </view>
    
    <!-- 預覽區域 -->
    <view class="preview-section" v-if="mediaUrl">
      <text class="preview-title">預覽</text>
      <image 
        v-if="mediaType === 'image'" 
        :src="mediaUrl" 
        mode="widthFix"
        class="preview-media"
      />
      <video 
        v-else-if="mediaType === 'video'" 
        :src="mediaUrl" 
        controls
        class="preview-media"
      />
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      cameraContext: null,
      devicePosition: 'back',
      flash: 'auto',
      mediaUrl: '',
      mediaType: '',
      flashModes: ['auto', 'on', 'off']
    }
  },
  onReady() {
    this.cameraContext = uni.createCameraContext()
  },
  methods: {
    onCameraError(e) {
      console.error('相機錯誤', e.detail)
      uni.showToast({
        title: '相機初始化失敗',
        icon: 'none'
      })
    },
    onCameraInit(e) {
      console.log('相機初始化完成', e.detail)
    },
    takePhoto() {
      this.cameraContext.takePhoto({
        quality: 'high',
        success: (res) => {
          this.mediaUrl = res.tempImagePath
          this.mediaType = 'image'
          console.log('拍照成功', res.tempImagePath)
        },
        fail: (err) => {
          console.error('拍照失敗', err)
          uni.showToast({
            title: '拍照失敗',
            icon: 'none'
          })
        }
      })
    },
    startRecord() {
      this.cameraContext.startRecord({
        success: () => {
          console.log('開始錄影')
          uni.showToast({
            title: '開始錄影',
            icon: 'none'
          })
        },
        fail: (err) => {
          console.error('開始錄影失敗', err)
        }
      })
    },
    stopRecord() {
      this.cameraContext.stopRecord({
        success: (res) => {
          this.mediaUrl = res.tempVideoPath
          this.mediaType = 'video'
          console.log('錄影完成', res.tempVideoPath)
          uni.showToast({
            title: '錄影完成',
            icon: 'success'
          })
        },
        fail: (err) => {
          console.error('停止錄影失敗', err)
        }
      })
    },
    switchCamera() {
      this.devicePosition = this.devicePosition === 'back' ? 'front' : 'back'
    },
    toggleFlash() {
      const currentIndex = this.flashModes.indexOf(this.flash)
      this.flash = this.flashModes[(currentIndex + 1) % this.flashModes.length]
    }
  }
}
</script>

<style>
.container {
  padding: 20px;
}
.camera {
  width: 100%;
  height: 300px;
  border-radius: 8px;
  background-color: #000;
}
.camera-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 20px 0;
}
.control-btn {
  flex: 1;
  min-width: 100px;
  padding: 10px;
  background-color: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 14px;
}
.preview-section {
  margin-top: 20px;
}
.preview-title {
  display: block;
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 10px;
}
.preview-media {
  width: 100%;
  border-radius: 8px;
}
</style>

掃碼功能

vue
<template>
  <view class="container">
    <camera
      class="camera"
      mode="scanCode"
      @scancode="onScanCode"
      @error="onCameraError"
    />
    
    <view class="scan-result" v-if="scanResult">
      <text class="result-title">掃碼結果:</text>
      <text class="result-content">{{ scanResult }}</text>
      <button @click="clearResult" class="clear-btn">清除結果</button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      scanResult: ''
    }
  },
  methods: {
    onScanCode(e) {
      this.scanResult = e.detail.result
      console.log('掃碼結果', e.detail.result)
      
      uni.showToast({
        title: '掃碼成功',
        icon: 'success'
      })
    },
    onCameraError(e) {
      console.error('相機錯誤', e.detail)
    },
    clearResult() {
      this.scanResult = ''
    }
  }
}
</script>

<style>
.scan-result {
  margin-top: 20px;
  padding: 15px;
  background-color: #f8f9fa;
  border-radius: 8px;
}
.result-title {
  display: block;
  font-weight: bold;
  margin-bottom: 10px;
}
.result-content {
  display: block;
  word-break: break-all;
  margin-bottom: 15px;
  color: #333;
}
.clear-btn {
  background-color: #dc3545;
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 4px;
}
</style>

live-player 直播播放元件

live-player 元件用於即時音影片播放,支援 RTMP、HTTP-FLV 等協定。

主要屬性

屬性名類型預設值說明
srcString-音影片地址
modeStringlive模式:live(直播)、RTC(即時通話)
autoplayBooleanfalse自動播放
mutedBooleanfalse是否靜音
orientationStringvertical畫面方向:vertical、horizontal
object-fitStringcontain填充模式:contain、fillCrop
background-muteBooleanfalse進入背景時是否靜音
min-cacheNumber1最小緩衝區(秒)
max-cacheNumber3最大緩衝區(秒)

使用範例

vue
<template>
  <view class="container">
    <live-player
      id="livePlayer"
      :src="liveUrl"
      mode="live"
      :autoplay="true"
      :muted="isMuted"
      :orientation="orientation"
      object-fit="contain"
      @statechange="onStateChange"
      @netstatus="onNetStatus"
      @error="onPlayerError"
      class="live-player"
    />
    
    <view class="player-info">
      <text class="info-item">狀態: {{ playerState }}</text>
      <text class="info-item">網路: {{ netStatus }}</text>
    </view>
    
    <view class="player-controls">
      <button @click="play" class="control-btn">播放</button>
      <button @click="pause" class="control-btn">暫停</button>
      <button @click="stop" class="control-btn">停止</button>
      <button @click="toggleMute" class="control-btn">
        {{ isMuted ? '取消靜音' : '靜音' }}
      </button>
      <button @click="toggleOrientation" class="control-btn">
        {{ orientation === 'vertical' ? '橫螢幕' : '直螢幕' }}
      </button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      livePlayerContext: null,
      liveUrl: 'https://example.com/live.flv',
      isMuted: false,
      orientation: 'vertical',
      playerState: '未知',
      netStatus: '未知'
    }
  },
  onReady() {
    this.livePlayerContext = uni.createLivePlayerContext('livePlayer', this)
  },
  methods: {
    onStateChange(e) {
      const stateMap = {
        2001: '已連接伺服器',
        2002: '已連接伺服器,開始拉流',
        2003: '網路接收到首個影片資料包',
        2004: '影片播放開始',
        2005: '影片播放進度',
        2006: '影片播放結束',
        2007: '影片播放Loading',
        2008: '解碼器啟動',
        2009: '影片解析度改變',
        '-2301': '網路斷連,且經多次重連搶救無效',
        '-2302': '取得加速拉流地址失敗',
        '2101': '目前影片幀解碼失敗',
        '2102': '目前音訊幀解碼失敗',
        '2103': '網路斷連, 已啟動自動重連',
        '2104': '網路來包不穩',
        '2105': '目前影片播放出現卡頓',
        '2106': '硬解啟動失敗,採用軟解',
        '2107': '目前影片幀不連續,可能丟幀',
        '2108': '目前流硬解第一個I幀失敗,SDK自動切軟解'
      }
      
      this.playerState = stateMap[e.detail.code] || `未知狀態: ${e.detail.code}`
      console.log('播放狀態變化', e.detail.code, this.playerState)
    },
    onNetStatus(e) {
      // 網路狀態資訊
      const info = e.detail.info
      this.netStatus = `頻寬: ${info.videoBitrate || 0}kbps`
      console.log('網路狀態', info)
    },
    onPlayerError(e) {
      console.error('直播播放錯誤', e.detail)
      uni.showToast({
        title: '播放失敗',
        icon: 'none'
      })
    },
    play() {
      this.livePlayerContext.play()
    },
    pause() {
      this.livePlayerContext.pause()
    },
    stop() {
      this.livePlayerContext.stop()
    },
    toggleMute() {
      this.isMuted = !this.isMuted
    },
    toggleOrientation() {
      this.orientation = this.orientation === 'vertical' ? 'horizontal' : 'vertical'
    }
  }
}
</script>

<style>
.container {
  padding: 20px;
}
.live-player {
  width: 100%;
  height: 200px;
  background-color: #000;
  border-radius: 8px;
}
.player-info {
  display: flex;
  justify-content: space-between;
  margin: 15px 0;
  padding: 10px;
  background-color: #f8f9fa;
  border-radius: 4px;
}
.info-item {
  font-size: 14px;
  color: #666;
}
.player-controls {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.control-btn {
  flex: 1;
  min-width: 80px;
  padding: 8px;
  background-color: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 14px;
}
</style>

live-pusher 直播推流元件

live-pusher 元件用於即時音影片錄製和推流,支援 RTMP 推流協定。

主要屬性

屬性名類型預設值說明
urlString-推流地址
modeStringRTC模式:SD(標清)、HD(高清)、FHD(超清)、RTC(即時通話)
autopushBooleanfalse自動推流
mutedBooleanfalse是否靜音
enable-cameraBooleantrue開啟攝影機
auto-focusBooleantrue自動對焦
orientationStringvertical畫面方向:vertical、horizontal
beautyNumber0美顏,取值範圍 0-9
whitenessNumber0美白,取值範圍 0-9
aspectString9:16寬高比:3:4、9:16
min-bitrateNumber200最小碼率
max-bitrateNumber1000最大碼率
audio-qualityStringhigh音訊品質:high、low
device-positionStringfront攝影機:front、back
remote-mirrorBooleanfalse設定推流畫面是否鏡像
local-mirrorStringauto控制本地預覽畫面是否鏡像

使用範例

vue
<template>
  <view class="container">
    <live-pusher
      id="livePusher"
      :url="pushUrl"
      mode="RTC"
      :autopush="false"
      :muted="isMuted"
      :enable-camera="enableCamera"
      :device-position="devicePosition"
      :beauty="beauty"
      :whiteness="whiteness"
      orientation="vertical"
      @statechange="onPushStateChange"
      @netstatus="onPushNetStatus"
      @error="onPushError"
      class="live-pusher"
    />
    
    <view class="pusher-info">
      <text class="info-item">推流狀態: {{ pushState }}</text>
      <text class="info-item">網路狀態: {{ pushNetStatus }}</text>
    </view>
    
    <view class="pusher-controls">
      <button @click="startPush" class="control-btn">開始推流</button>
      <button @click="stopPush" class="control-btn">停止推流</button>
      <button @click="pausePush" class="control-btn">暫停推流</button>
      <button @click="resumePush" class="control-btn">恢復推流</button>
      <button @click="switchCamera" class="control-btn">切換攝影機</button>
      <button @click="toggleMute" class="control-btn">
        {{ isMuted ? '取消靜音' : '靜音' }}
      </button>
    </view>
    
    <view class="beauty-controls">
      <view class="beauty-item">
        <text>美顏: {{ beauty }}</text>
        <slider 
          :value="beauty" 
          :max="9" 
          :min="0" 
          @change="onBeautyChange"
          class="beauty-slider"
        />
      </view>
      <view class="beauty-item">
        <text>美白: {{ whiteness }}</text>
        <slider 
          :value="whiteness" 
          :max="9" 
          :min="0" 
          @change="onWhitenessChange"
          class="beauty-slider"
        />
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      livePusherContext: null,
      pushUrl: 'rtmp://example.com/live/stream',
      isMuted: false,
      enableCamera: true,
      devicePosition: 'front',
      beauty: 0,
      whiteness: 0,
      pushState: '未知',
      pushNetStatus: '未知'
    }
  },
  onReady() {
    this.livePusherContext = uni.createLivePusherContext('livePusher', this)
  },
  methods: {
    onPushStateChange(e) {
      const stateMap = {
        1001: '已經連接推流伺服器',
        1002: '已經與伺服器握手完畢,開始推流',
        1003: '打開攝影機成功',
        1004: '錄螢幕啟動成功',
        1005: '推流動態調整解析度',
        1006: '推流動態調整碼率',
        1007: '首幀畫面採集完成',
        1008: '編碼器啟動',
        '-1301': '打開攝影機失敗',
        '-1302': '打開麥克風失敗',
        '-1303': '影片編碼失敗',
        '-1304': '音訊編碼失敗',
        '-1305': '不支援的影片解析度',
        '-1306': '不支援的音訊取樣率',
        '-1307': '網路斷連,且經多次重連搶救無效',
        '-1308': '開始錄螢幕失敗,可能是被使用者拒絕',
        '-1309': '錄螢幕失敗,不支援的Android系統版本,需要5.0以上的系統',
        '-1310': '錄螢幕被其他應用程式打斷了',
        '-1311': 'Android Mic打開成功,但是錄不到音訊資料',
        '-1312': '錄螢幕動態切橫直螢幕失敗',
        '1101': '網路狀況不佳:上行頻寬太小,上傳資料受阻',
        '1102': '網路斷連, 已啟動自動重連',
        '1103': '硬編碼啟動失敗,採用軟編碼',
        '1104': 'VideoToolbox 編碼失敗',
        '1105': '新美顏軟編碼啟動失敗,採用舊的軟編碼',
        '1106': '新美顏軟編碼啟動失敗,採用舊的軟編碼',
        '3001': 'RTMP -DNS解析失敗',
        '3002': 'RTMP伺服器連接失敗',
        '3003': 'RTMP伺服器握手失敗',
        '3004': 'RTMP伺服器主動斷開,請檢查推流地址的合法性或防盜鏈有效期',
        '3005': 'RTMP 讀/寫失敗'
      }
      
      this.pushState = stateMap[e.detail.code] || `未知狀態: ${e.detail.code}`
      console.log('推流狀態變化', e.detail.code, this.pushState)
    },
    onPushNetStatus(e) {
      const info = e.detail.info
      this.pushNetStatus = `上行: ${info.netSpeed || 0}KB/s`
      console.log('推流網路狀態', info)
    },
    onPushError(e) {
      console.error('推流錯誤', e.detail)
      uni.showToast({
        title: '推流失敗',
        icon: 'none'
      })
    },
    startPush() {
      this.livePusherContext.start()
    },
    stopPush() {
      this.livePusherContext.stop()
    },
    pausePush() {
      this.livePusherContext.pause()
    },
    resumePush() {
      this.livePusherContext.resume()
    },
    switchCamera() {
      this.livePusherContext.switchCamera()
      this.devicePosition = this.devicePosition === 'front' ? 'back' : 'front'
    },
    toggleMute() {
      this.isMuted = !this.isMuted
    },
    onBeautyChange(e) {
      this.beauty = e.detail.value
    },
    onWhitenessChange(e) {
      this.whiteness = e.detail.value
    }
  }
}
</script>

<style>
.live-pusher {
  width: 100%;
  height: 300px;
  background-color: #000;
  border-radius: 8px;
}
.pusher-info {
  display: flex;
  justify-content: space-between;
  margin: 15px 0;
  padding: 10px;
  background-color: #f8f9fa;
  border-radius: 4px;
}
.pusher-controls {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 20px;
}
.control-btn {
  flex: 1;
  min-width: 80px;
  padding: 8px;
  background-color: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 12px;
}
.beauty-controls {
  background-color: #f8f9fa;
  padding: 15px;
  border-radius: 8px;
}
.beauty-item {
  margin-bottom: 15px;
}
.beauty-item text {
  display: block;
  margin-bottom: 8px;
  font-size: 14px;
  color: #333;
}
.beauty-slider {
  width: 100%;
}
</style>

總結

本指南介紹了 uni-app 中的主要媒體元件:

圖片元件

  • image:支援多種格式和縮放模式,提供懶載入功能

音影片元件

  • video:功能豐富的影片播放器,支援彈幕、全螢幕等功能
  • audio:音訊播放元件,可自訂播放器介面

相機元件

  • camera:支援拍照、錄影和掃碼功能

直播元件

  • live-player:直播播放,支援多種直播協定
  • live-pusher:直播推流,支援美顏和多種推流設定

這些媒體元件為 uni-app 應用程式提供了完整的多媒體處理能力,能夠滿足大部分應用場景的需求。在使用時需要注意各平台的相容性和權限要求。

一次開發,多端部署 - 讓跨平台開發更簡單