uni-app 媒體元件指南
本指南詳細介紹 uni-app 的媒體元件,包括圖片、音訊、影片、相機等多媒體功能元件的使用方法。
目錄
image 圖片元件
image 元件用於展示圖片,支援 JPG、PNG、SVG、WEBP、GIF 等格式。
主要屬性
| 屬性名 | 類型 | 預設值 | 說明 |
|---|---|---|---|
| src | String | - | 圖片資源地址 |
| mode | String | scaleToFill | 圖片裁剪、縮放的模式 |
| lazy-load | Boolean | false | 圖片懶載入 |
| fade-show | Boolean | true | 圖片顯示動畫效果 |
| webp | Boolean | false | 是否解析 webP 格式 |
| show-menu-by-longpress | Boolean | false | 長按顯示識別選單 |
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 元件用於播放影片,支援多種影片格式和豐富的播放控制功能。
主要屬性
| 屬性名 | 類型 | 預設值 | 說明 |
|---|---|---|---|
| src | String | - | 影片資源地址 |
| autoplay | Boolean | false | 是否自動播放 |
| loop | Boolean | false | 是否循環播放 |
| muted | Boolean | false | 是否靜音播放 |
| controls | Boolean | true | 是否顯示預設播放控制項 |
| poster | String | - | 影片封面圖片地址 |
| initial-time | Number | 0 | 指定影片初始播放位置(秒) |
| duration | Number | - | 指定影片時長(秒) |
| object-fit | String | contain | 影片填充模式 |
| show-fullscreen-btn | Boolean | true | 是否顯示全螢幕按鈕 |
| show-play-btn | Boolean | true | 是否顯示播放按鈕 |
| show-center-play-btn | Boolean | true | 是否顯示影片中間的播放按鈕 |
| enable-progress-gesture | Boolean | true | 是否開啟控制進度的手勢 |
| danmu-list | Array | - | 彈幕列表 |
| danmu-btn | Boolean | false | 是否顯示彈幕按鈕 |
| enable-danmu | Boolean | false | 是否展示彈幕 |
使用範例
基礎影片播放
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 元件用於播放音訊檔案,支援多種音訊格式。
主要屬性
| 屬性名 | 類型 | 預設值 | 說明 |
|---|---|---|---|
| id | String | - | 音訊元件唯一識別符 |
| src | String | - | 音訊資源地址 |
| loop | Boolean | false | 是否循環播放 |
| controls | Boolean | false | 是否顯示預設控制項 |
| poster | String | - | 音訊封面圖片地址 |
| name | String | 未知音訊 | 音訊名稱 |
| author | String | 未知作者 | 作者名稱 |
| autoplay | Boolean | false | 是否自動播放 |
使用範例
基礎音訊播放
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 元件用於呼叫裝置攝影機進行拍照或錄影。
主要屬性
| 屬性名 | 類型 | 預設值 | 說明 |
|---|---|---|---|
| mode | String | normal | 模式:normal(拍照/錄影)、scanCode(掃碼) |
| resolution | String | medium | 解析度:low、medium、high |
| device-position | String | back | 攝影機:front(前置)、back(後置) |
| flash | String | auto | 閃光燈:auto、on、off |
| frame-size | String | medium | 期望的相機幀資料尺寸 |
使用範例
基礎相機功能
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 等協定。
主要屬性
| 屬性名 | 類型 | 預設值 | 說明 |
|---|---|---|---|
| src | String | - | 音影片地址 |
| mode | String | live | 模式:live(直播)、RTC(即時通話) |
| autoplay | Boolean | false | 自動播放 |
| muted | Boolean | false | 是否靜音 |
| orientation | String | vertical | 畫面方向:vertical、horizontal |
| object-fit | String | contain | 填充模式:contain、fillCrop |
| background-mute | Boolean | false | 進入背景時是否靜音 |
| min-cache | Number | 1 | 最小緩衝區(秒) |
| max-cache | Number | 3 | 最大緩衝區(秒) |
使用範例
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 推流協定。
主要屬性
| 屬性名 | 類型 | 預設值 | 說明 |
|---|---|---|---|
| url | String | - | 推流地址 |
| mode | String | RTC | 模式:SD(標清)、HD(高清)、FHD(超清)、RTC(即時通話) |
| autopush | Boolean | false | 自動推流 |
| muted | Boolean | false | 是否靜音 |
| enable-camera | Boolean | true | 開啟攝影機 |
| auto-focus | Boolean | true | 自動對焦 |
| orientation | String | vertical | 畫面方向:vertical、horizontal |
| beauty | Number | 0 | 美顏,取值範圍 0-9 |
| whiteness | Number | 0 | 美白,取值範圍 0-9 |
| aspect | String | 9:16 | 寬高比:3:4、9:16 |
| min-bitrate | Number | 200 | 最小碼率 |
| max-bitrate | Number | 1000 | 最大碼率 |
| audio-quality | String | high | 音訊品質:high、low |
| device-position | String | front | 攝影機:front、back |
| remote-mirror | Boolean | false | 設定推流畫面是否鏡像 |
| local-mirror | String | auto | 控制本地預覽畫面是否鏡像 |
使用範例
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 應用程式提供了完整的多媒體處理能力,能夠滿足大部分應用場景的需求。在使用時需要注意各平台的相容性和權限要求。