c
复制代码
// VideoRecorder.ets
import camera from '@ohos.multimedia.camera';
import media from '@ohos.multimedia.media';
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
import { BusinessError } from '@ohos.base';
@Entry
@Component
struct VideoRecorderPage {
// 状态变量
@State previewSurfaceId: string = '';
@State isRecording: boolean = false;
@State isPreviewing: boolean = false;
@State recordingTime: string = '00:00';
@State videoPath: string = '';
@State flashMode: camera.FlashMode = camera.FlashMode.FLASH_MODE_OFF;
// 相机相关
private cameraManager: camera.CameraManager | null = null;
private cameraInput: camera.CameraInput | null = null;
private previewOutput: camera.PreviewOutput | null = null;
private videoOutput: camera.VideoOutput | null = null;
private cameraObj: camera.CameraDevice | null = null;
// 录制相关
private recorder: media.MediaRecorder | null = null;
private timer: number = 0;
private elapsedSeconds: number = 0;
aboutToAppear() {
this.initCamera();
}
aboutToDisappear() {
this.releaseResources();
}
// 初始化相机
async initCamera() {
try {
// 获取相机管理器
this.cameraManager = await camera.getCameraManager(globalThis.abilityContext as common.Context);
// 获取相机列表
const cameras = this.cameraManager.getSupportedCameras();
if (cameras.length === 0) {
console.error('No camera found');
return;
}
// 选择后置摄像头
const backCamera = cameras.find(camera => camera.position === camera.CameraPosition.CAMERA_POSITION_BACK) || cameras[0];
// 创建相机输入
this.cameraInput = this.cameraManager.createCameraInput(backCamera);
await this.cameraInput.open();
// 创建相机会话
await this.createCameraSession();
// 开始预览
await this.startPreview();
} catch (error) {
console.error('Camera initialization failed:', error);
}
}
// 创建相机会话
async createCameraSession() {
if (!this.cameraManager || !this.cameraInput) {
throw new Error('Camera not initialized');
}
// 获取相机输出能力
const cameraOutputCap = this.cameraManager.getSupportedOutputCapability(
this.cameraInput.cameraDevice,
camera.SceneMode.NORMAL_VIDEO
);
// 创建预览输出
const previewProfiles = cameraOutputCap.previewProfiles;
if (previewProfiles.length === 0) {
throw new Error('No preview profile available');
}
this.previewOutput = this.cameraManager.createPreviewOutput(
previewProfiles[0],
this.previewSurfaceId
);
// 创建视频输出
const videoProfiles = cameraOutputCap.videoProfiles;
if (videoProfiles.length > 0) {
this.videoOutput = this.cameraManager.createVideoOutput(videoProfiles[0]);
}
// 创建相机会话
const session = this.cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO);
// 配置会话
session.beginConfig();
session.addInput(this.cameraInput);
session.addOutput(this.previewOutput);
if (this.videoOutput) {
session.addOutput(this.videoOutput);
}
await session.commitConfig();
this.cameraObj = this.cameraInput.cameraDevice;
return session;
}
// 开始预览
async startPreview() {
if (!this.cameraObj) {
return;
}
const session = this.cameraObj.session;
await session.start();
this.isPreviewing = true;
console.log('Camera preview started');
}
// 开始录制视频
async startRecording() {
if (!this.videoOutput || this.isRecording) {
return;
}
try {
// 准备录制参数
const context = globalThis.abilityContext as common.Context;
const filesDir = context.filesDir;
const timestamp = new Date().getTime();
this.videoPath = `${filesDir}/video_${timestamp}.mp4`;
// 创建录制器
this.recorder = await media.createMediaRecorder();
// 配置录制参数
const recorderConfig: media.MediaRecorderOptions = {
videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
profile: {
audioBitrate: 128000,
audioChannels: 2,
audioCodec: media.CodecMimeType.AUDIO_AAC,
audioSampleRate: 48000,
fileFormat: media.ContainerFormatType.CFT_MPEG_4,
videoBitrate: 2000000,
videoCodec: media.CodecMimeType.VIDEO_AVC,
videoFrameWidth: 1920,
videoFrameHeight: 1080,
videoFrameRate: 30
},
url: this.videoPath,
rotation: 0,
location: { latitude: 0, longitude: 0 }
};
// 准备录制器
await this.recorder.prepare(recorderConfig);
// 获取视频表面并绑定到视频输出
const surfaceId = await this.recorder.getInputSurface();
this.videoOutput?.start().then(() => {
this.videoOutput?.bindSurface(surfaceId).then(() => {
// 开始录制
this.recorder?.start();
this.isRecording = true;
this.startTimer();
console.log('Recording started:', this.videoPath);
});
});
} catch (error) {
console.error('Start recording failed:', error);
}
}
// 停止录制
async stopRecording() {
if (!this.recorder || !this.isRecording) {
return;
}
try {
// 停止录制
await this.recorder.stop();
// 释放录制器
await this.recorder.release();
this.recorder = null;
// 解绑视频表面
if (this.videoOutput) {
await this.videoOutput.unbindSurface();
}
this.isRecording = false;
this.stopTimer();
console.log('Recording stopped. Video saved at:', this.videoPath);
} catch (error) {
console.error('Stop recording failed:', error);
}
}
// 开始计时器
startTimer() {
this.elapsedSeconds = 0;
this.recordingTime = '00:00';
this.timer = setInterval(() => {
this.elapsedSeconds++;
const minutes = Math.floor(this.elapsedSeconds / 60);
const seconds = this.elapsedSeconds % 60;
this.recordingTime = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}, 1000) as unknown as number;
}
// 停止计时器
stopTimer() {
if (this.timer) {
clearInterval(this.timer);
this.timer = 0;
}
this.recordingTime = '00:00';
}
// 切换闪光灯
async toggleFlash() {
if (!this.cameraInput) return;
try {
const flashModes = [
camera.FlashMode.FLASH_MODE_OFF,
camera.FlashMode.FLASH_MODE_ON,
camera.FlashMode.FLASH_MODE_AUTO,
camera.FlashMode.FLASH_MODE_ALWAYS_OPEN
];
const currentIndex = flashModes.indexOf(this.flashMode);
const nextIndex = (currentIndex + 1) % flashModes.length;
this.flashMode = flashModes[nextIndex];
await this.cameraInput.setFlashMode(this.flashMode);
console.log('Flash mode changed to:', this.flashMode);
} catch (error) {
console.error('Toggle flash failed:', error);
}
}
// 切换摄像头
async switchCamera() {
if (this.isRecording) {
console.log('Cannot switch camera while recording');
return;
}
try {
await this.releaseResources();
await this.initCamera();
} catch (error) {
console.error('Switch camera failed:', error);
}
}
// 释放资源
releaseResources() {
// 停止录制
if (this.isRecording) {
this.stopRecording();
}
// 停止预览
if (this.cameraObj) {
const session = this.cameraObj.session;
session.stop();
session.release();
}
// 关闭相机输入
if (this.cameraInput) {
this.cameraInput.close();
}
// 停止计时器
this.stopTimer();
this.isPreviewing = false;
this.isRecording = false;
}
build() {
Column({ space: 0 }) {
// 相机预览区域
Stack() {
// 相机预览
XComponent({
id: 'cameraPreview',
type: 'surface',
controller: new XComponentController()
})
.width('100%')
.height('100%')
.onLoad((xComponentContext?: XComponentContext) => {
if (xComponentContext) {
this.previewSurfaceId = xComponentContext.getXComponentSurfaceId();
console.log('Surface ID:', this.previewSurfaceId);
}
})
// 录制计时器
if (this.isRecording) {
Text(this.recordingTime)
.fontSize(24)
.fontColor(Color.White)
.backgroundColor('#66000000')
.padding({ left: 20, right: 20, top: 10, bottom: 10 })
.borderRadius(20)
.margin({ top: 50 })
}
// 录制指示灯
if (this.isRecording) {
Row() {
Circle({ width: 12, height: 12 })
.fill(Color.Red)
}
.margin({ top: 50, left: '50%' })
.translate({ x: 50 })
}
// 控制按钮区域(底部)
Column({ space: 20 }) {
// 顶部控制栏
Row() {
// 闪光灯按钮
Button(this.getFlashIcon())
.width(50)
.height(50)
.backgroundColor('#33000000')
.borderRadius(25)
.onClick(() => {
this.toggleFlash();
})
// 切换摄像头按钮
Button('↻')
.width(50)
.height(50)
.backgroundColor('#33000000')
.borderRadius(25)
.onClick(() => {
this.switchCamera();
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({ left: 20, right: 20 })
// 中央录制按钮
Row() {
if (this.isRecording) {
// 录制中按钮
Button('')
.width(80)
.height(80)
.backgroundColor(Color.Red)
.borderRadius(40)
.onClick(() => {
this.stopRecording();
})
} else {
// 开始录制按钮
Button('')
.width(70)
.height(70)
.backgroundColor(Color.Red)
.borderRadius(35)
.border({ width: 4, color: Color.White })
.onClick(() => {
this.startRecording();
})
}
}
.width('100%')
.justifyContent(FlexAlign.Center)
// 状态信息
Text(this.isRecording ? '录制中...' : '准备录制')
.fontSize(16)
.fontColor(Color.White)
.backgroundColor('#33000000')
.padding(10)
.borderRadius(5)
}
.width('100%')
.position({ x: 0, y: '80%' })
}
.width('100%')
.height('80%')
.backgroundColor(Color.Black)
// 视频列表区域
if (this.videoPath) {
List() {
ListItem() {
Row({ space: 15 }) {
// 视频缩略图(实际应用中需要生成缩略图)
Image($r('app.media.video_thumb'))
.width(60)
.height(60)
.borderRadius(8)
Column({ space: 5 }) {
Text('最新录制')
.fontSize(16)
.fontColor(Color.Black)
Text(this.videoPath)
.fontSize(12)
.fontColor(Color.Gray)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.layoutWeight(1)
Button('播放')
.width(60)
.height(30)
.fontSize(12)
.onClick(() => {
this.playVideo(this.videoPath);
})
}
.width('100%')
.padding(10)
}
}
.width('100%')
.height('20%')
.divider({ strokeWidth: 1, color: '#F1F1F1' })
}
}
.width('100%')
.height('100%')
}
// 获取闪光灯图标
getFlashIcon(): string {
switch (this.flashMode) {
case camera.FlashMode.FLASH_MODE_OFF:
return '⚡️';
case camera.FlashMode.FLASH_MODE_ON:
return '⚡️';
case camera.FlashMode.FLASH_MODE_AUTO:
return '⚡️A';
case camera.FlashMode.FLASH_MODE_ALWAYS_OPEN:
return '⚡️●';
default:
return '⚡️';
}
}
// 播放视频
async playVideo(videoPath: string) {
try {
// 使用媒体播放器播放视频
const context = globalThis.abilityContext as common.Context;
// 这里可以跳转到视频播放页面,或者使用弹窗播放
console.log('Play video:', videoPath);
// 示例:使用系统能力打开视频
// 实际应用中可能需要使用媒体播放器组件
promptAction.showToast({
message: `播放视频: ${videoPath}`,
duration: 2000
});
} catch (error) {
console.error('Play video failed:', error);
}
}
}
c
复制代码
// VideoSettings.ets - 视频设置
@Component
struct VideoSettings {
@State videoQuality: number = 2; // 0:低, 1:中, 2:高
@State frameRate: number = 30;
@State audioEnabled: boolean = true;
build() {
Column({ space: 20 }) {
Text('视频设置')
.fontSize(20)
.fontWeight(FontWeight.Bold)
// 视频质量选择
Text('视频质量')
Row({ space: 10 }) {
ForEach([0, 1, 2], (quality) => {
Button(this.getQualityText(quality))
.backgroundColor(this.videoQuality === quality ? Color.Blue : Color.Gray)
.fontColor(Color.White)
.onClick(() => {
this.videoQuality = quality;
})
})
}
// 帧率选择
Text(`帧率: ${this.frameRate}fps`)
Slider({
value: this.frameRate,
min: 15,
max: 60,
step: 1,
style: SliderStyle.OutSet
})
.onChange((value: number) => {
this.frameRate = value;
})
// 音频开关
Toggle({ type: ToggleType.Checkbox, isOn: this.audioEnabled })
.onChange((isOn: boolean) => {
this.audioEnabled = isOn;
})
Text('启用音频')
}
.padding(20)
}
getQualityText(quality: number): string {
switch (quality) {
case 0: return '低质量';
case 1: return '中等质量';
case 2: return '高质量';
default: return '未知';
}
}
}