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应用提供了完整的多媒体处理能力,能够满足大部分应用场景的需求。在使用时需要注意各平台的兼容性和权限要求。