uni-app 媒体功能

核心知识点

图片选择与上传

uni-app 提供了多种 API 用于图片的选择、拍摄和上传。

1. 选择图片

// 从相册选择图片
uni.chooseImage({
  count: 9, // 最多选择9张
  sizeType: ['original', 'compressed'], // 原图或压缩图
  sourceType: ['album', 'camera'], // 相册或相机
  success: (res) => {
    console.log('选择成功:', res.tempFilePaths);
    // 上传图片
    uploadImages(res.tempFilePaths);
  },
  fail: (err) => {
    console.log('选择失败:', err);
  }
});

// 上传图片
function uploadImages(tempFilePaths) {
  tempFilePaths.forEach((filePath, index) => {
    uni.uploadFile({
      url: 'https://api.example.com/upload',
      filePath: filePath,
      name: 'file',
      formData: {
        'index': index
      },
      success: (res) => {
        console.log('上传成功:', res.data);
      },
      fail: (err) => {
        console.log('上传失败:', err);
      }
    });
  });
}

2. 拍摄图片

// 调用相机拍摄
uni.chooseImage({
  count: 1,
  sizeType: ['original', 'compressed'],
  sourceType: ['camera'], // 仅相机
  success: (res) => {
    console.log('拍摄成功:', res.tempFilePaths[0]);
  },
  fail: (err) => {
    console.log('拍摄失败:', err);
  }
});

3. 预览图片

// 预览图片
uni.previewImage({
  current: 'https://example.com/image.jpg', // 当前显示的图片链接
  urls: ['https://example.com/image1.jpg', 'https://example.com/image2.jpg'], // 需要预览的图片链接列表
  success: (res) => {
    console.log('预览成功');
  },
  fail: (err) => {
    console.log('预览失败:', err);
  }
});

音频视频处理

uni-app 提供了音频和视频的录制、播放和处理功能。

1. 音频录制与播放

// 开始录音
uni.startRecord({
  success: () => {
    console.log('开始录音');
  },
  fail: (err) => {
    console.log('开始录音失败:', err);
  }
});

// 停止录音
uni.stopRecord({
  success: (res) => {
    console.log('录音成功:', res.tempFilePath);
    // 播放录音
    playAudio(res.tempFilePath);
  },
  fail: (err) => {
    console.log('录音失败:', err);
  }
});

// 播放音频
function playAudio(filePath) {
  const innerAudioContext = uni.createInnerAudioContext();
  innerAudioContext.src = filePath;
  innerAudioContext.play();
  
  innerAudioContext.onPlay(() => {
    console.log('开始播放');
  });
  
  innerAudioContext.onEnded(() => {
    console.log('播放结束');
  });
  
  innerAudioContext.onError((err) => {
    console.log('播放失败:', err);
  });
}

2. 视频录制与播放

// 拍摄视频
uni.chooseVideo({
  sourceType: ['camera'],
  maxDuration: 60, // 最长60秒
  camera: 'back', // 后置摄像头
  success: (res) => {
    console.log('拍摄成功:', res.tempFilePath);
    // 上传视频
    uploadVideo(res.tempFilePath);
  },
  fail: (err) => {
    console.log('拍摄失败:', err);
  }
});

// 上传视频
function uploadVideo(tempFilePath) {
  uni.uploadFile({
    url: 'https://api.example.com/upload',
    filePath: tempFilePath,
    name: 'file',
    formData: {
      'type': 'video'
    },
    success: (res) => {
      console.log('上传成功:', res.data);
    },
    fail: (err) => {
      console.log('上传失败:', err);
    }
  });
}

// 播放视频
uni.createVideoContext('myVideo').play();

相机操作

uni-app 提供了相机相关的 API,用于控制相机的各项功能。

1. 相机上下文

// 创建相机上下文
const cameraContext = uni.createCameraContext();

// 拍摄照片
cameraContext.takePhoto({
  quality: 'high',
  success: (res) => {
    console.log('拍摄成功:', res.tempImagePath);
  },
  fail: (err) => {
    console.log('拍摄失败:', err);
  }
});

// 开始录像
cameraContext.startRecord({
  success: () => {
    console.log('开始录像');
  },
  fail: (err) => {
    console.log('开始录像失败:', err);
  }
});

// 停止录像
cameraContext.stopRecord({
  success: (res) => {
    console.log('录像成功:', res.tempVideoPath);
  },
  fail: (err) => {
    console.log('录像失败:', err);
  }
});

2. 扫码功能

// 扫码
uni.scanCode({
  onlyFromCamera: false, // 是否仅从相机扫码
  scanType: ['barCode', 'qrCode'], // 扫码类型
  success: (res) => {
    console.log('扫码成功:', res.result);
  },
  fail: (err) => {
    console.log('扫码失败:', err);
  }
});

实用案例

实现图片上传功能

1. 单图上传

<template>
  <view class="upload-container">
    <view class="upload-area" @click="chooseImage">
      <image v-if="imageUrl" :src="imageUrl" mode="aspectFill"></image>
      <view v-else class="upload-placeholder">
        <text>+ 选择图片</text>
      </view>
    </view>
    <button v-if="imageUrl" class="upload-button" @click="uploadImage">上传图片</button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      imageUrl: '',
      tempFilePath: ''
    };
  },
  methods: {
    chooseImage() {
      uni.chooseImage({
        count: 1,
        sizeType: ['original', 'compressed'],
        sourceType: ['album', 'camera'],
        success: (res) => {
          this.tempFilePath = res.tempFilePaths[0];
          this.imageUrl = res.tempFilePaths[0];
        },
        fail: (err) => {
          console.log('选择失败:', err);
          uni.showToast({
            title: '选择失败',
            icon: 'none'
          });
        }
      });
    },
    uploadImage() {
      if (!this.tempFilePath) {
        uni.showToast({
          title: '请先选择图片',
          icon: 'none'
        });
        return;
      }

      uni.showLoading({
        title: '上传中...'
      });

      uni.uploadFile({
        url: 'https://api.example.com/upload',
        filePath: this.tempFilePath,
        name: 'file',
        formData: {
          'type': 'avatar'
        },
        success: (res) => {
          console.log('上传成功:', res.data);
          const data = JSON.parse(res.data);
          if (data.code === 0) {
            uni.showToast({
              title: '上传成功',
              icon: 'success'
            });
            // 保存服务器返回的图片地址
            this.imageUrl = data.data.url;
          } else {
            uni.showToast({
              title: '上传失败',
              icon: 'none'
            });
          }
        },
        fail: (err) => {
          console.log('上传失败:', err);
          uni.showToast({
            title: '上传失败',
            icon: 'none'
          });
        },
        complete: () => {
          uni.hideLoading();
        }
      });
    }
  }
};
</script>

<style scoped>
.upload-container {
  padding: 20rpx;
}

.upload-area {
  width: 300rpx;
  height: 300rpx;
  border: 1rpx dashed #ddd;
  border-radius: 8rpx;
  overflow: hidden;
  margin-bottom: 20rpx;
}

.upload-area image {
  width: 100%;
  height: 100%;
}

.upload-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #f5f5f5;
}

.upload-placeholder text {
  font-size: 28rpx;
  color: #999;
}

.upload-button {
  width: 300rpx;
  height: 80rpx;
  background-color: #007aff;
  color: #fff;
  border-radius: 8rpx;
  font-size: 28rpx;
}
</style>

2. 多图上传

<template>
  <view class="multi-upload-container">
    <view class="upload-list">
      <view v-for="(item, index) in images" :key="index" class="upload-item">
        <image :src="item.url" mode="aspectFill"></image>
        <view class="delete-btn" @click="deleteImage(index)">
          <text>×</text>
        </view>
      </view>
      <view v-if="images.length < 9" class="upload-item upload-add" @click="chooseImages">
        <text>+</text>
      </view>
    </view>
    <button v-if="images.length > 0" class="upload-button" @click="uploadImages">上传图片</button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      images: []
    };
  },
  methods: {
    chooseImages() {
      uni.chooseImage({
        count: 9 - this.images.length, // 最多选择9张
        sizeType: ['original', 'compressed'],
        sourceType: ['album', 'camera'],
        success: (res) => {
          // 添加选择的图片
          const newImages = res.tempFilePaths.map(path => ({
            url: path,
            uploaded: false
          }));
          this.images = [...this.images, ...newImages];
        },
        fail: (err) => {
          console.log('选择失败:', err);
          uni.showToast({
            title: '选择失败',
            icon: 'none'
          });
        }
      });
    },
    deleteImage(index) {
      this.images.splice(index, 1);
    },
    uploadImages() {
      if (this.images.length === 0) {
        uni.showToast({
          title: '请先选择图片',
          icon: 'none'
        });
        return;
      }

      uni.showLoading({
        title: '上传中...'
      });

      let uploadedCount = 0;
      const totalCount = this.images.length;

      this.images.forEach((item, index) => {
        if (!item.uploaded) {
          uni.uploadFile({
            url: 'https://api.example.com/upload',
            filePath: item.url,
            name: 'file',
            formData: {
              'index': index
            },
            success: (res) => {
              console.log('上传成功:', res.data);
              const data = JSON.parse(res.data);
              if (data.code === 0) {
                // 更新为服务器返回的地址
                this.images[index].url = data.data.url;
                this.images[index].uploaded = true;
                uploadedCount++;
                
                if (uploadedCount === totalCount) {
                  uni.hideLoading();
                  uni.showToast({
                    title: '全部上传成功',
                    icon: 'success'
                  });
                }
              } else {
                uni.hideLoading();
                uni.showToast({
                  title: '上传失败',
                  icon: 'none'
                });
              }
            },
            fail: (err) => {
              console.log('上传失败:', err);
              uni.hideLoading();
              uni.showToast({
                title: '上传失败',
                icon: 'none'
              });
            }
          });
        } else {
          uploadedCount++;
        }
      });
    }
  }
};
</script>

<style scoped>
.multi-upload-container {
  padding: 20rpx;
}

.upload-list {
  display: flex;
  flex-wrap: wrap;
  margin-bottom: 20rpx;
}

.upload-item {
  width: 220rpx;
  height: 220rpx;
  margin: 10rpx;
  position: relative;
  border-radius: 8rpx;
  overflow: hidden;
}

.upload-item image {
  width: 100%;
  height: 100%;
}

.upload-add {
  border: 1rpx dashed #ddd;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #f5f5f5;
}

.upload-add text {
  font-size: 48rpx;
  color: #999;
}

.delete-btn {
  position: absolute;
  top: 0;
  right: 0;
  width: 40rpx;
  height: 40rpx;
  background-color: rgba(0, 0, 0, 0.5);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 32rpx;
  border-radius: 0 8rpx 0 20rpx;
}

.upload-button {
  width: 100%;
  height: 80rpx;
  background-color: #007aff;
  color: #fff;
  border-radius: 8rpx;
  font-size: 28rpx;
  margin-top: 20rpx;
}
</style>

实现音频录制与播放

<template>
  <view class="audio-container">
    <view class="audio-control">
      <button v-if="!isRecording" class="record-button" @click="startRecord">开始录音</button>
      <button v-else class="stop-button" @click="stopRecord">停止录音</button>
    </view>
    
    <view v-if="audioUrl" class="audio-playback">
      <text>录音文件: {{ audioUrl }}</text>
      <button class="play-button" @click="playAudio">播放录音</button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      isRecording: false,
      audioUrl: '',
      innerAudioContext: null
    };
  },
  onLoad() {
    // 创建音频上下文
    this.innerAudioContext = uni.createInnerAudioContext();
    
    this.innerAudioContext.onPlay(() => {
      console.log('开始播放');
    });
    
    this.innerAudioContext.onEnded(() => {
      console.log('播放结束');
    });
    
    this.innerAudioContext.onError((err) => {
      console.log('播放失败:', err);
      uni.showToast({
        title: '播放失败',
        icon: 'none'
      });
    });
  },
  methods: {
    startRecord() {
      uni.startRecord({
        success: () => {
          this.isRecording = true;
          uni.showToast({
            title: '开始录音',
            icon: 'none'
          });
        },
        fail: (err) => {
          console.log('开始录音失败:', err);
          uni.showToast({
            title: '开始录音失败',
            icon: 'none'
          });
        }
      });
    },
    stopRecord() {
      uni.stopRecord({
        success: (res) => {
          this.isRecording = false;
          this.audioUrl = res.tempFilePath;
          uni.showToast({
            title: '录音成功',
            icon: 'success'
          });
        },
        fail: (err) => {
          console.log('录音失败:', err);
          this.isRecording = false;
          uni.showToast({
            title: '录音失败',
            icon: 'none'
          });
        }
      });
    },
    playAudio() {
      if (!this.audioUrl) return;
      
      this.innerAudioContext.src = this.audioUrl;
      this.innerAudioContext.play();
    }
  },
  onUnload() {
    // 销毁音频上下文
    if (this.innerAudioContext) {
      this.innerAudioContext.destroy();
    }
  }
};
</script>

<style scoped>
.audio-container {
  padding: 20rpx;
}

.audio-control {
  margin-bottom: 30rpx;
}

.record-button {
  width: 200rpx;
  height: 80rpx;
  background-color: #ff3b30;
  color: #fff;
  border-radius: 8rpx;
  font-size: 28rpx;
}

.stop-button {
  width: 200rpx;
  height: 80rpx;
  background-color: #34c759;
  color: #fff;
  border-radius: 8rpx;
  font-size: 28rpx;
}

.audio-playback {
  margin-top: 30rpx;
  padding: 20rpx;
  background-color: #f5f5f5;
  border-radius: 8rpx;
}

.audio-playback text {
  display: block;
  margin-bottom: 20rpx;
  font-size: 28rpx;
  color: #333;
}

.play-button {
  width: 200rpx;
  height: 80rpx;
  background-color: #007aff;
  color: #fff;
  border-radius: 8rpx;
  font-size: 28rpx;
}
</style>

实现相机扫码功能

<template>
  <view class="scan-container">
    <button class="scan-button" @click="scanCode">扫码</button>
    <view v-if="scanResult" class="result-container">
      <text class="result-label">扫码结果:</text>
      <text class="result-text">{{ scanResult }}</text>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      scanResult: ''
    };
  },
  methods: {
    scanCode() {
      uni.scanCode({
        onlyFromCamera: false,
        scanType: ['barCode', 'qrCode'],
        success: (res) => {
          console.log('扫码成功:', res.result);
          this.scanResult = res.result;
          uni.showToast({
            title: '扫码成功',
            icon: 'success'
          });
        },
        fail: (err) => {
          console.log('扫码失败:', err);
          uni.showToast({
            title: '扫码失败',
            icon: 'none'
          });
        }
      });
    }
  }
};
</script>

<style scoped>
.scan-container {
  padding: 20rpx;
}

.scan-button {
  width: 200rpx;
  height: 80rpx;
  background-color: #007aff;
  color: #fff;
  border-radius: 8rpx;
  font-size: 28rpx;
  margin-bottom: 30rpx;
}

.result-container {
  padding: 20rpx;
  background-color: #f5f5f5;
  border-radius: 8rpx;
}

.result-label {
  display: block;
  margin-bottom: 10rpx;
  font-size: 28rpx;
  font-weight: bold;
  color: #333;
}

.result-text {
  font-size: 28rpx;
  color: #666;
  word-break: break-all;
}
</style>

学习目标

通过本集的学习,你应该能够:

  1. 掌握 uni-app 图片选择与上传的 API 使用方法
  2. 学会音频和视频的录制、播放和处理
  3. 熟悉相机操作和扫码功能的实现
  4. 能够通过实用案例实现图片上传、音频录制与播放、相机扫码等功能
  5. 了解媒体功能的最佳实践,提高应用的用户体验

小结

媒体功能是 uni-app 开发中的重要部分,合理的媒体功能实现可以提高应用的用户体验和交互性。通过本集的学习,你已经掌握了图片选择与上传、音频视频处理、相机操作等核心知识点,并通过实际案例了解了如何实现图片上传、音频录制与播放、相机扫码等功能。在后续的开发中,你可以根据实际需求,灵活运用这些知识,构建更加功能强大的应用。

« 上一篇 uni-app 本地存储 下一篇 » uni-app 地图与定位