Merge branch 'master' of gitee.com:openharmony/applications_app_samples

This commit is contained in:
revin-sun 2023-11-25 11:38:04 +08:00
commit b82d35b993
776 changed files with 22451 additions and 1049 deletions

2
README_zh.md Executable file → Normal file
View File

@ -433,7 +433,7 @@
<td ></td>
</tr>
<tr>
<![if supportMisalignedColumns]>
<![if supportMisalignedColumns]>
<tr height="18" style="display:none;">
</tr>
<![endif]>

View File

@ -17,6 +17,7 @@ import hilog from '@ohos.hilog';
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
import { Driver, ON } from '@ohos.UiTest';
import inputMethod from '@ohos.inputMethod';
const DRIVER = Driver.create();
const BUNDLE = 'http';
@ -121,6 +122,10 @@ export default function abilityTest() {
let ipAddress = await DRIVER.findComponent(ON.id('GET'));
await ipAddress.inputText('https://baidu.com');
await DRIVER.delayMs(1000);
// 停止会话,关闭键盘
let inputMethodController = inputMethod.getController();
inputMethodController.stopInputSession();
await DRIVER.delayMs(1000);
let submitBtn = await DRIVER.findComponent(ON.id('submit'));
await submitBtn.click();
await DRIVER.delayMs(1000);

View File

@ -98,7 +98,7 @@
```
git init
git config core.sparsecheckout true
echo code/BasicFeature/Notification/CustomNotification/ > .git/info/sparse-checkout
echo code/BasicFeature/Connectivity/StageSocket/ > .git/info/sparse-checkout
git remote add origin https://gitee.com/openharmony/applications_app_samples.git
git pull origin master
```

View File

@ -1,10 +1,14 @@
## 上传下载应用服务器使用说明
### HFS下载地址
https://nchc.dl.sourceforge.net/project/hfs/HFS/2.3m/hfs.exe
### 服务器配置:
1.测试设备和电脑连接同一个局域网。
2.电脑打开本目录下`hfs.exe`可执行文件。
2.电脑打开下载好的`hfs.exe`可执行文件。
3.hfs客户端配置

Binary file not shown.

View File

@ -17,6 +17,7 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
import { Component, Driver, ON } from '@ohos.UiTest';
import hilog from '@ohos.hilog';
import inputMethod from '@ohos.inputMethod';
const BUNDLE = 'WebSocket'
const TAG = '[Sample_WebSocket]'
@ -92,6 +93,9 @@ export default function appTest() {
let btnBind = await driver.findComponent(ON.id('btn_send'))
await btnBind.click()
await driver.delayMs(500)
// 停止会话,关闭键盘
let inputMethodController = inputMethod.getController();
inputMethodController.stopInputSession();
// 校验内容
hilog.info(DOMAIN, TAG, BUNDLE + ' sendMessage_001 check message')
@ -147,6 +151,9 @@ export default function appTest() {
hilog.info(DOMAIN, TAG, BUNDLE + ' sendMessage_002 send')
await btnBind.click()
await driver.delayMs(500)
// 停止会话,关闭键盘
let inputMethodController = inputMethod.getController();
inputMethodController.stopInputSession();
// 校验内容
hilog.info(DOMAIN, TAG, BUNDLE + ' sendMessage_002 check message')
@ -193,6 +200,9 @@ export default function appTest() {
let btnBind = await driver.findComponent(ON.id('btn_send'))
await btnBind.click()
await driver.delayMs(500)
// 停止会话,关闭键盘
let inputMethodController = inputMethod.getController();
inputMethodController.stopInputSession();
// 校验内容
hilog.info(DOMAIN, TAG, BUNDLE + ' sendMessage_003 check message')

View File

@ -48,9 +48,9 @@
7.设备状态感知框架:点击按钮分别实现订阅设备状态服务、取消订阅设备状态服务、查询设备状态并显示功能。
5.热管理:进入热管理页面,展示当前设备热档位信息、设备过热时提示用户。
8.热管理:进入热管理页面,展示当前设备热档位信息、设备过热时提示用户。
6.USB管理打开监听开关后插入USB设备会有提示并刷新设备列表关闭监听后插入USB设备不会提示和自动刷新下拉设备列表可以手动刷新设备列表。
9.USB管理打开监听开关后插入USB设备会有提示并刷新设备列表关闭监听后插入USB设备不会提示和自动刷新下拉设备列表可以手动刷新设备列表。
### 工程目录

View File

@ -20,7 +20,7 @@ class BasicDataSource implements IDataSource {
return 0;
}
public getData(index: number): ESObject {
public getData(index: number): Object | undefined {
return undefined;
}

View File

@ -16,7 +16,6 @@
{
"apiType": 'stageMode',
"buildOption": {
"compileMode": "esmodule"
},
"targets": [
{

View File

@ -37,6 +37,30 @@ export default function abilityTest() {
// 删除测试过程中创建的文件
afterAll(async () => {
})
/**
* 拉起相机拍照
*/
it(BUNDLE + 'TakePhoto_001', 0, async (done: Function) => {
HiLog.info(DOMAIN, TAG, BUNDLE + 'TakePhoto_001 begin');
let driver = Driver.create();
let abilityDelegatorRegistry = AbilityDelegatorRegistry.getAbilityDelegator();
try {
await abilityDelegatorRegistry.startAbility({
bundleName: 'com.ohos.camera',
abilityName: 'com.ohos.camera.MainAbility'
});
await driver.delayMs(4000);
// 坐标基于rk3568
await driver.click(360, 1090);
await driver.delayMs(1000);
done();
} catch (err) {
expect(0).assertEqual(err.code);
done();
}
HiLog.info(DOMAIN, TAG, BUNDLE + 'TakePhoto_001 end');
})
/**
* 打开应用
*/
@ -117,21 +141,15 @@ export default function abilityTest() {
let btnAlbum = await driver.findComponent(ON.text(await getResourceString($r('app.string.album_camera'))));
await btnAlbum.click();
await driver.delayMs(3000);
let btnItem = await driver.findComponents(ON.type('GridItem'));
if (btnItem == null || btnItem.length == 0 || btnItem == undefined) {
await driver.assertComponentExist(ON.text(await getResourceString($r('app.string.no_photo'))));
} else {
await driver.assertComponentExist(ON.type('GridItem'));
// 点击图片
await driver.click(100, 200);
await driver.delayMs(1000);
HiLog.info(DOMAIN, TAG, BUNDLE + 'check text' + await getResourceString($r('app.string.btn_favor')));
await driver.assertComponentExist(ON.text(await getResourceString($r('app.string.btn_favor'))));
HiLog.info(DOMAIN, TAG, BUNDLE + 'check text' + await getResourceString($r('app.string.btn_rename')));
await driver.assertComponentExist(ON.text(await getResourceString($r('app.string.btn_rename'))));
HiLog.info(DOMAIN, TAG, BUNDLE + 'check text' + await getResourceString($r('app.string.btn_delete')));
await driver.assertComponentExist(ON.text(await getResourceString($r('app.string.btn_delete'))));
}
// 点击图片
await driver.click(100, 200);
await driver.delayMs(1000);
HiLog.info(DOMAIN, TAG, BUNDLE + 'check text' + await getResourceString($r('app.string.btn_favor')));
await driver.assertComponentExist(ON.text(await getResourceString($r('app.string.btn_favor'))));
HiLog.info(DOMAIN, TAG, BUNDLE + 'check text' + await getResourceString($r('app.string.btn_rename')));
await driver.assertComponentExist(ON.text(await getResourceString($r('app.string.btn_rename'))));
HiLog.info(DOMAIN, TAG, BUNDLE + 'check text' + await getResourceString($r('app.string.btn_delete')));
await driver.assertComponentExist(ON.text(await getResourceString($r('app.string.btn_delete'))));
})
/**
* 重命名文件

View File

@ -4,6 +4,7 @@
| 测试功能 |预置条件|输入|预期输出|是否自动|测试结果|
|---------|-------------------------|--------------------------------|--------------------------------|--------------------------------|--------------------------------|
| 拉起相机 |设备相机正常|点击拍照|成功拍照|是|Pass|
| 拉起应用 |设备正常运行| |成功拉起应用|是|Pass|
| 申请权限 |成功拉起应用| |弹出提示框|是|Pass|
| 主页展示 |使用相机应用拍照,权限申请成功| |展示系统相册(相机,视频,截屏录屏,我的收藏,最近删除),默认缩略图|否|Pass|

View File

@ -18,6 +18,7 @@ import { Driver, ON, MatchPattern } from '@ohos.UiTest';
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
import I18n from '@ohos.i18n';
import Logger from '../Logger';
import inputMethod from '@ohos.inputMethod';
const BUNDLE = "International_";
const TAG: string = '[Sample_International]';
@ -130,6 +131,11 @@ export default function abilityTest() {
// 文本断点处理
let longTextInput = await driver.findComponent(ON.id('long_text_input'));
await longTextInput.inputText('text processing test');
await driver.delayMs(100);
// 停止会话,关闭键盘
let inputMethodController = inputMethod.getController();
inputMethodController.stopInputSession();
await driver.delayMs(100);
let longTextButton = await driver.findComponent(ON.text(await getResourceString($r('app.string.text_breakpoint'))));
await longTextButton.click();
await driver.delayMs(100);

View File

@ -0,0 +1,11 @@
/node_modules
/oh_modules
/local.properties
/.idea
**/build
/.hvigor
.cxx
/.clangd
/.clang-format
/.clang-tidy
**/.test

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
{
"app": {
"bundleName": "com.samples.videoplayer",
"vendor": "example",
"versionCode": 1000000,
"versionName": "1.0.0",
"icon": "$media:app_icon",
"label": "$string:app_name"
}
}

View File

@ -0,0 +1,8 @@
{
"string": [
{
"name": "app_name",
"value": "AVCastDemo"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -0,0 +1,116 @@
# 视频播放
### 介绍
本示例主要展示了网络视频播放的相关功能。使用[@ohos.multimedia.avsession]等接口实现视频播放的功能。
### 效果预览
| 主页 |
|-------------------------------- |
| ![Index](screenshots/device/index.jpeg) |
#### 使用说明
1. 点击播放按钮,应用的播放状态发生变化。
2. 点击暂停按钮,应用的播放状态开始变化。
3. 点击上一个按钮,界面展示播放列表中的上一个视频的信息。
4. 点击下一下按钮,界面展示播放列表中的下一个视频的信息。
### 工程目录
给出项目中关键的目录结构并描述它们的作用,示例如下:
```
entry/src/main/ets/
|---common // 方法封装
|---|---AudioFrameworkTest.ets
|---|---AudioUtils.ets // 控制器封装
|---|---CommonUtils.ets // 格式化时间封装
|---|---Constants.ets // 媒体资源信息
|---|---PermissionUtils.ets // 权限封装
|---entryability
|---|---EntryAbility.ets
|---pages
|---|---Index.ets // 界面实现
|---|---components
|---|---|---SongItem.ets // 视频列表组件
```
### 具体实现
* 界面相关的实现都封装在pages/Index.ets下源码参考[pages/Index.ets](./entry/src/main/ets/pages/Index.ets)
* 使用`@State`来设置与逻辑代码同步更新的变量,当逻辑代码中对应的变量更新时,界面会同步的刷新。
* 通过引入逻辑代码对应的类创建出对象实现对onClick事件的响应关键代码段
```js
import media from '@ohos.multimedia.media'; // 引入
this.avPlayer = await media.createAVPlayer(); // 创建对象
this.controller = await this.session.getController(); // 通过类的对象来调用逻辑代码
```
* 逻辑相关的实现都封装在common/MediaController.ets下源码参考[common/AudioUtils.ets](./entry/src/main/ets/common/AudioUtils.ets)
应用的初始化相关操作
* 链接变量
使用`@State`来设置与逻辑代码同步更新,关键代码段:
```ets
@State session: avSession.AVSession = null;
@State controller: avSession.AVSessionController = null;
private avPlayer: media.AVPlayer;
@State @Watch('playInfoUpdated') currentPlayInfo: avSession.AVMediaDescription = undefined;
this.currentPlayInfo = temp;
this.avPlayer = await this.audioUtils.init();
```
* 获取当前设备中会话并创建Controller
通过接口`audioUtils.init()`获取当前设备中的媒体会话;
通过接口`session.getController()`创建媒体会话对应的控制器;
通过接口`on(play | pause | stop | playNext | playPrevious | seek)`开启对远程以及播控中心提供方发送事件的监听,对事件进行处理;
应用在运行中相关的操作
* 从远程以及播控中心获取基础控制命令
基础控制命令可以通过监听事件`setListenerForMesFromController()`。本示例中,从媒体控制方到媒体提供方的基础控制命令主要包括`play, pause, playPrevious, playNext`。发送命令的参考代码如下:
```ets
let command : AVSessionManager.AVControlCommand = {
command : 'play',
parameter : undefined
} // 构造AVControlCommand参数
async setListenerForMesFromController(); // 媒体会话控制器与媒体会话一一对应
```
* 获取自定义会话数据(以获取视频为例)
> 说明:
>
> 本示例中,用户点击“下一个视频”的命令,会在将视频信息更新。
视频使用接口`switchToNextByLoopMode()`更新视频信息,示例代码如下:
```ets
this.currentIndex = this.currentIndex === this.songList.length - 1 ? 0 : this.currentIndex + 1;
this.updateCurrentPlayInfo(this.songList[this.currentIndex], this.audioType);
```
### 相关权限
不涉及
### 约束与限制
1. 本示例仅支持标准系统上运行。
2. 本示例为Stage模型支持API10版本SDKSDK版本号(API Version 10 Release),镜像版本号(4.0 Release)
3. 本示例需要使用DevEco Studio 版本号(4.0 Release)及以上版本才可编译运行。

View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
{
"app": {
"signingConfigs": [],
"products": [
{
"name": "default",
"signingConfig": "default",
"compileSdkVersion": 10,
"compatibleSdkVersion": 10,
"runtimeOS": "OpenHarmony"
}
],
"buildModeSet": [
{
"name": "debug",
},
{
"name": "release"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
}
]
}

View File

@ -0,0 +1,6 @@
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
{
"apiType": "stageMode",
"buildOption": {
},
"targets": [
{
"name": "default"
},
{
"name": "ohosTest",
}
]
}

View File

@ -0,0 +1,6 @@
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
{
"license": "",
"devDependencies": {},
"author": "",
"name": "entry",
"description": "Please describe the basic information.",
"main": "",
"version": "1.0.0",
"dependencies": {}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fs from '@ohos.file.fs';
const TAG = "AVCastDemo";
export default class AudioFrameworkTest {
public constructor() {
console.log(TAG, 'start audio framework test init');
let context = getContext(this);
let path = context.filesDir;
// 使用沙箱路径获取文件,实际路径为/data/app/el2/100/base/com.samples.videoplayer/haps/entry/files/sample_3s.wav
const filePath = path + '/sample_3s.wav';
console.log(TAG, 'file path: ' + filePath);
fs.stat(filePath, (err, state) => {
console.log(TAG, 'get file state succeed: size = ' + state?.size + ', err = ' + err);
})
console.log(TAG, 'try open file: ' + filePath);
let file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
console.log(TAG, 'try open file succeed: ' + file.fd);
fs.close(file);
}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import media from '@ohos.multimedia.media';
import common from '@ohos.app.ability.common';
const TAG = 'AVCastDemo';
export default class AudioUtils {
public avPlayer: media.AVPlayer | null = null;
public state: string = '';
public surfaceId: string = '';
private callbackMap: Map<string, Function> = new Map();
constructor() {
console.log(TAG, `AVPlayer seek succeede in `);
}
public async init() {
this.avPlayer = await media.createAVPlayer();
this.setAVPlayerCallback();
return this.avPlayer;
}
setAVPlayerCallback() {
if (!this.avPlayer) {
console.log(TAG, 'no acPlayer');
return;
}
this.avPlayer.on('seekDone', (seekDOneTime) => {
console.log(TAG, `AVPlayer seek succeeded, seek time is ${seekDOneTime}`);
})
this.avPlayer.on('error', (err) => {
console.error(TAG, `Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
this.avPlayer?.reset();
})
this.avPlayer.on('stateChange', async (state) => {
this.state = state;
switch (state) {
case 'idle':
console.log(TAG, 'AVPlayer state idle called.');
this.callbackMap = new Map();
break;
case 'initialized':
console.log(TAG, 'AVPlayer state initialized called.');
if (this.avPlayer && this.surfaceId) {
this.avPlayer.surfaceId = this.surfaceId;
}
try {
this.avPlayer?.prepare().then(() => {
console.log(TAG, 'AVPlayer prepare succeeded.');
});
} catch(err) {
console.log(TAG, `Invoke prepare failed, err : ${JSON.stringify(err)}`)
}
break;
case 'prepared':
console.log(TAG, 'AVPlayer state prepare called.');
if (this.callbackMap.get('prepared')) {
this.callbackMap.get('prepared');
console.log(TAG, 'AVPlayer state prepare start.');
this.callbackMap.set('prepared', null);
}
break;
case 'playing':
console.log(TAG, 'AVPlayer state playing called.');
break;
case 'paused':
console.log(TAG, 'AVPlayer state paused called.');
break;
case 'completed':
console.log(TAG, 'AVPlayer state completed called.');
if (this.callbackMap.get('completed')) {
this.callbackMap.get('completed');
console.log(TAG, 'AVPlayer state completed start.');
}
break;
case 'stopped':
console.log(TAG, 'AVPlayer state stopped called.');
break;
case 'released':
console.log(TAG, 'AVPlayer state released called.');
break;
default:
console.log(TAG, 'AVPlayer state unknown called.');
break;
}
})
}
public on(event: string, callback: Function) {
this.callbackMap.set(event, callback)
}
public async loadFromSrcFd(fileDescriptor: media.AVFileDescriptor) {
console.log(TAG, 'loadFromSrcFd: ' + JSON.stringify(fileDescriptor));
if (!this.avPlayer) {
console.log(TAG, 'no acPlayer');
return;
}
if (this.state !== 'idle') {
await this.avPlayer.reset();
}
this.avPlayer.fdSrc = fileDescriptor;
return this.avPlayer;
}
public async loadFromRawFile(fileName: string) {
console.log(TAG, 'loadFromRawFile: ' + fileName);
if (!this.avPlayer) {
console.log(TAG, 'no avplayer');
return;
}
if (this.state !== 'idle') {
await this.avPlayer.reset();
}
const context = getContext(this) as common.UIAbilityContext;
const fileDescriptor = await context.resourceManager.getRawFd(fileName);
console.log(TAG, 'fileDescriptor: ' + fileDescriptor);
this.avPlayer.fdSrc = fileDescriptor;
return this.avPlayer;
}
public async loadFromNetwork(url: string) {
console.log(TAG, 'loadFromNetwork: ' + url);
if (!this.avPlayer) {
console.log(TAG, 'no avplayer');
return;
}
if (this.state !== 'idle') {
await this.avPlayer.reset();
}
this.avPlayer.url = url;
return this.avPlayer;
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export default class CommonUtils {
public static millSecond2Minutes(time: number) {
const min = Math.floor(time / 1000 / 60);
const sec = Math.ceil(time / 1000 % 60);
return `${CommonUtils.paddingString(min)}:${CommonUtils.paddingString(sec)}`;
}
static paddingString(value: number) {
if (value < 10) {
return `0${value}`;
} else {
return value;
}
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import avSession from '@ohos.multimedia.avsession';
export default class Constants {
static THEME_COLOR = '#ffffffff';
static URL_VIDEO_LIST: Array<avSession.AVMediaDescription> = [
{
assetId: 'VIDEO-1-h264_1500k_mp4',
title: 'h264_1500k_mp4',
artist: 'Rain',
mediaUri: 'https://mazwai.com/videvo_files/video/free/2015-05/small_watermarked/benjamin_wu--raccoon_come_and_go_preview.webm',
mediaType: 'VIDEO',
mediaSize: 1000,
startPosition: 0,
duration: 100000,
mediaImage: 'https://cdn.pixabay.com/photo/2023/10/28/09/20/darling-8346954_1280.jpg',
albumTitle: ' 《Trxye - EP》 ',
appName: 'Spotify',
},
{
assetId: 'VIDEO-2-oceans',
title: 'oceans.mp4',
artist: 'Rain',
mediaUri: 'https://mazwai.com/videvo_files/video/free/2014-08/small_watermarked/clint_melander--a_day_without_rain_preview.webm',
mediaType: 'VIDEO',
mediaSize: 1000,
startPosition: 0,
duration: 100000,
mediaImage: 'https://cdn.pixabay.com/photo/2023/10/20/13/49/beach-8329531_1280.jpg',
albumTitle: ' 《Trxye - EP》 ',
appName: 'Spotify',
},
];
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type image from '@ohos.multimedia.image';
export class PixelMapWrapper {
pixelMap: image.PixelMap = undefined;
width: number = 0;
height: number = 0;
constructor(pixelMap: image.PixelMap, width: number, height: number) {
this.pixelMap = pixelMap;
this.width = width;
this.height = height;
}
release(): void {
if (this.pixelMap !== null && this.pixelMap !== undefined) {
this.pixelMap.release();
}
this.pixelMap = undefined;
this.width = 0;
this.height = 0;
}
}
export class Size {
width: number = 0
height: number = 0
}
export enum CircleAngle {
ONE_QUARTER_CIRCLE_ANGLE = 90,
HALF_CIRCLE_ANGEL = 180,
THREE_QUARTER_CIRCLE_ANGLE = 270,
CIRCLE_ANGLE = 360
}
export class GlobalContext {
private constructor() {};
private static instance: GlobalContext;
private _objects = new Map<string, Object>();
public static getContext(): GlobalContext {
if (!GlobalContext.instance) {
GlobalContext.instance = new GlobalContext();
}
return GlobalContext.instance;
}
getObject(value: string): Object | undefined {
return this._objects.get(value);
}
setObject(key: string, objectClass: Object): void {
this._objects.set(key, objectClass);
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
const TAG = 'AVPlayer';
const permissions: Array<Permissions> = ['ohos.permission.READ_AUDIO'];
export default class PermissionUtils {
public static init(context: Context, callback: Function) {
console.log(TAG, 'start init permission');
let atManager = abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i ++) {
if(grantStatus[i] !== 0) {
console.info(TAG, `User reject permission: ${grantStatus[i]}`);
callback(false);
return;
}
}
console.info(TAG, `get permission succeed`);
callback(true);
}).catch((err: Error) => {
console.error(TAG, `Failed to request permissions from user. err : ${JSON.stringify(err)}`);
})
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import hilog from '@ohos.hilog';
import UIAbility from '@ohos.app.ability.UIAbility';
import Want from '@ohos.app.ability.Want';
import window from '@ohos.window';
import Constants from '../common/Constants';
import { GlobalContext } from '../common/MediaAssetThumbnailLoader';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate', JSON.stringify(want), JSON.stringify(launchParam));
GlobalContext.getContext().setObject('context', this.context);
}
onDestroy() {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage) {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
windowStage.getMainWindow().then(windowClass => {
try {
windowClass.setWindowSystemBarProperties({
statusBarColor: Constants.THEME_COLOR,
statusBarContentColor: '#000000',
}, (err) => {
if (err.code) {
console.error('Failed to set the system bar properties. Cause: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in setting the system bar properties.');
})
let orientation = window.Orientation.AUTO_ROTATION;
windowClass.setPreferredOrientation(orientation, (err) => {
if (err.code) {
console.error('Failed to set window orientation. Cause: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in setting window orientation.');
})
} catch (exception) {
console.error('Failed to set system bar to be invisible. Cause: ' + JSON.stringify(exception));
}
})
}
onWindowStageDestroy() {
// Main window is destroyed, release UI related resources
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground() {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground() {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}

View File

@ -0,0 +1,970 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import avSession from '@ohos.multimedia.avsession';
import media from '@ohos.multimedia.media';
import promptAction from '@ohos.promptAction';
import CommonUtils from '../common/CommonUtils';
import common from '@ohos.app.ability.common';
import AudioUtils from '../common/AudioUtils';
import connection from '@ohos.net.connection';
import wantAgent from '@ohos.app.ability.wantAgent';
import Constants from '../common/Constants';
import AVCastPicker from '@ohos.multimedia.avCastPicker';
import audio from '@ohos.multimedia.audio';
import fs from '@ohos.file.fs';
import util from '@ohos.util';
import image from '@ohos.multimedia.image';
import { BusinessError } from '@ohos.base';
const TAG = 'AVCastDemo';
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
@State outputDevice: avSession.OutputDeviceInfo = {devices: []};
@State outputDeviceInfo: avSession.OutputDeviceInfo = {devices: []};
@State castController: avSession.AVCastController | undefined = undefined;
@State castControllerSession: avSession.AVSessionController | undefined = undefined;
@State session: avSession.AVSession | undefined = undefined;
@State controller: avSession.AVSessionController | undefined = undefined;
@State albumImage: image.PixelMap | undefined = undefined;
@State playType: 'local' | 'cast' = 'local';
@State playState: number = -1;
@State isFavorMap: Map<string, boolean> = new Map();
@State volume: number = 0 ;
@State seedPosition: number = 0;
@State duration: number = 0;
@State private currentIndex: number = 0;
@State @Watch('playInfoUpdated') currentPlayInfo: avSession.AVMediaDescription | undefined = undefined;
@State currentMediaId: string = '';
@State currentLoopMode: number = 2;
@State hasNetwork: boolean = false;
@State isProgressSliding: boolean = false;
@State audioType: 'url' | 'rawfile' | 'scan' | 'video' = 'url';
private audioUtils: AudioUtils = new AudioUtils();
private avPlayer: media.AVPlayer | undefined = undefined;
private audioVolumeGroupManager: audio.AudioVolumeGroupManager | undefined = undefined;
private localAudioRation = 1;
private audioManager: audio.AudioManager | undefined = undefined;
private netCon?: connection.NetConnection;
private sliderTimer?: number;
private mXComponentController: XComponentController = new XComponentController();
@State private songList: Array<avSession.AVMediaDescription> = [];
private urlVideoList: Array<avSession.AVMediaDescription> = Constants.URL_VIDEO_LIST;
async aboutToAppear() {
console.log(TAG, 'about to appear');
this.songList = this.urlVideoList;
this.audioType = 'video';
this.currentPlayInfo = this.urlVideoList[0];
this.avPlayer = await this.audioUtils.init();
this.avPlayer?.on('audioInterrupt', (info: audio.InterruptEvent) => {
console.info(TAG, 'audioInterrupt success, and InterruptEvent info is: ' + info);
if (this.avPlayer?.state === 'playing') {
console.info(TAG, 'audio interrupt, start pause');
this.avPlayer?.pause();
this.setPlayState(avSession.PlaybackState.PLAYBACK_STATE_PAUSE);
promptAction.showToast({ message: 'audio interrupt, pause done' });
}
})
this.avPlayer?.on('timeUpdate', (time: number) => {
console.info(TAG, 'timeUpdate time: ' + time);
if (!this.isProgressSliding) {
if (this.duration == 0) {
this.seedPosition = 0;
} else {
this.seedPosition = time / this.duration * 100;
}
const params: avSession.AVPlaybackState = {
position: {
elapsedTime: time,
updateTime: new Date().getTime()
},
};
this.session?.setAVPlaybackState(params);
}
})
this.avPlayer?.on('durationUpdate', (duration: number) => {
console.info(TAG, 'durationUpdate duration: ' + duration);
this.duration = duration;
if (this.duration !== 0) {
const playMetaData: avSession.AVMetadata = {
assetId: this.currentPlayInfo?.assetId as string, // origin assetId
title: this.currentPlayInfo?.title as string,
artist: this.currentPlayInfo?.artist as string,
mediaImage: this.albumImage, // origin mediaImage
album: this.currentPlayInfo?.albumTitle as string,
duration: this.duration
}
this.session?.setAVMetadata(playMetaData);
}
})
this.avPlayer?.on('videoSizeChange', (width: number, height: number) => {
console.info(TAG, 'videoSizeChange success, and width is: ' + width + ', height is: ' + height);
})
await this.setAudioManager();
await this.autoStartAll(false);
this.addNetworkListener();
this.readLRCFile();
console.info(TAG, 'about to appear done: ' + !!this.avPlayer);
}
readLRCFile(): void {
const context = getContext(this) as common.UIAbilityContext;
context.resourceManager.getRawFileContent('test.lrc', (error: BusinessError, value: Uint8Array) => {
if (error != null) {
console.log(TAG, 'error is: ' + error);
} else {
let rawFile = value;
let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
let retStr = textDecoder.decodeWithStream(rawFile, { stream: false });
console.log(TAG, 'get lrc file: ' + retStr);
}
});
}
addNetworkListener(): void {
console.log(TAG, 'start add Network Listener');
this.netCon = connection.createNetConnection();
this.netCon?.register((error: BusinessError) => {
console.error(TAG, 'network error: ' + JSON.stringify(error));
})
connection.getAllNets().then(data => {
console.log(TAG, 'get all network: ' + JSON.stringify(data));
this.hasNetwork = data?.length > 0;
})
this.netCon?.on('netAvailable', data => {
console.log(TAG, 'network Available: ' + JSON.stringify(data));
this.hasNetwork = true;
})
this.netCon?.on('netLost', data => {
console.log(TAG, 'network Lost: ' + JSON.stringify(data));
connection.getAllNets().then(data => {
console.log(TAG, 'get all network: ' + JSON.stringify(data));
this.hasNetwork = data?.length > 0;
});
})
}
onPageHide() {
console.log(TAG, 'indexPage onPAgeHide in.');
}
async playInfoUpdated() {
console.log(TAG, 'playInfoUpdated: ' + JSON.stringify(this.currentPlayInfo));
this.currentMediaId = this.currentPlayInfo?.assetId as string;
this.albumImage = this.currentPlayInfo?.mediaImage as image.PixelMap
if (this.playType === 'local') {
await this.setLocalMediaInfo();
} else {
await this.setRemoteMediaInfo();
}
console.log(TAG, 'playInfoUpdate: done')
}
async setRemoteMediaInfo() {
console.log(TAG, 'set remote media info: ' + JSON.stringify(this.currentPlayInfo) + ', ' + this.currentIndex);
let queueItem: avSession.AVQueueItem = {
itemId: this.currentIndex,
description: this.currentPlayInfo
};
await this.castController?.prepare(queueItem);
const isPlaying = this.playState === avSession.PlaybackState.PLAYBACK_STATE_PLAY;
if (isPlaying) {
await this.castController?.start(queueItem);
await this.setPlayState(avSession.PlaybackState.PLAYBACK_STATE_PLAY);
}
if (this.audioType === 'scan') {
const playMetaData: avSession.AVMetadata = {
assetId: this.currentPlayInfo?.assetId as string, // origin assetId
title: this.currentPlayInfo?.title as string,
artist: this.currentPlayInfo?.artist as string,
mediaImage: this.albumImage, // origin mediaImage
album: this.currentPlayInfo?.albumTitle as string,
duration: this.duration,
};
console.log(TAG, 'try set AV Metadata while cast for scan: ' + JSON.stringify(playMetaData));
this.session?.setAVMetadata(playMetaData);
}
console.log(TAG, 'set remote media info done');
}
async setLocalMediaInfo() {
console.log(TAG, 'set local media info: ' + JSON.stringify(this.currentPlayInfo));
if (!this.session) {
console.log(TAG, 'set local media info: no session');
return;
}
if (this.audioUtils) {
const isPlaying = this.playState === avSession.PlaybackState.PLAYBACK_STATE_PLAY;
this.setPlayState(avSession.PlaybackState.PLAYBACK_STATE_PAUSE, this.currentPlayInfo?.assetId,
!!this.isFavorMap[this.currentPlayInfo?.assetId as string]);
console.log(TAG, 'set play state pause');
if (this.audioType === 'url' || this.audioType === 'video') {
await this.audioUtils.loadFromNetwork(this.currentPlayInfo?.mediaUri as string);
} else if (this.audioType === 'rawfile') {
await this.audioUtils.loadFromNetwork(this.currentPlayInfo?.mediaUri as string);
} else if (this.audioType === 'scan') {
await this.audioUtils.loadFromSrcFd(this.currentPlayInfo?.fdSrc as media.AVFileDescriptor);
}
console.log(TAG, 'local local audio done: ' + isPlaying + ', ' + this.playType + ', ' + this.avPlayer?.state);
if (isPlaying) {
this.audioUtils.on('prepared', () => {
console.log(TAG, 'AVPlayer state prepare, state play');
if (this.playType === 'local') {
this.localPlayOrPause();
} else {
this.remotePlayOrPause();
}
});
}
this.audioUtils.on('completed', () => {
console.log(TAG, 'AVPlayer state completed, state to stop');
this.setPlayState(avSession.PlaybackState.PLAYBACK_STATE_PAUSE);
});
} else {
console.log(TAG, 'set local media fail: no audioUtils');
}
this.albumImage = this.currentPlayInfo?.mediaImage as image.PixelMap;
const playMetaData: avSession.AVMetadata = {
assetId: this.currentPlayInfo?.assetId as string, // origin assetId
title: this.currentPlayInfo?.title as string,
artist: this.currentPlayInfo?.artist as string,
mediaImage: this.albumImage, // origin mediaImage
album: this.currentPlayInfo?.albumTitle as string,
duration: this.duration,
};
console.log(TAG, 'try set AV Metadata: ' + JSON.stringify(playMetaData));
this.session?.setAVMetadata(playMetaData);
console.log(TAG, 'set AV Metadata: ');
}
aboutToDisappear() {
console.log(TAG, 'about to disappear');
if (this.controller) {
this.controller.off('outputDeviceChange');
this.controller.destroy();
}
if (this.castController) {
this.castController?.off('playbackStateChange');
this.castController?.off('error');
this.castController?.off('playPrevious');
this.castController?.off('playNext');
}
try {
if (this.session) {
this.session?.stopCasting();
this.session?.destroy();
}
} catch (err) {
console.log(TAG, err);
}
if (this.avPlayer) {
this.avPlayer?.release();
}
// 使用unregister接口取消订阅
this.netCon?.unregister((error) => {
console.log(JSON.stringify(error));
})
}
async setAudioManager() {
console.log(TAG, 'try get audio manger');
const audioManager = audio.getAudioManager();
if (!audioManager) {
console.error(TAG, 'get audio manager fail: fail get audioManager');
return;
}
this.audioManager = audioManager;
const volumeManager = audioManager.getVolumeManager();
if (!volumeManager) {
console.error(TAG, 'get audio manager fail: fail get volumeManager');
return;
}
volumeManager.on('volumeChange', (volumeEvent) => {
console.info(`VolumeType of stream : ${JSON.stringify(volumeEvent)}`);
let type: audio.AudioVolumeType = volumeEvent.volumeType;
let num: number = volumeEvent.volume;
if(type == audio.AudioVolumeType.MEDIA && this.playType === 'local') {
this.volume = num / this.localAudioRation;
}
});
this.audioVolumeGroupManager = await volumeManager.getVolumeGroupManager(audio.DEFAULT_VOLUME_GROUP_ID);
if (!this.audioVolumeGroupManager) {
console.error(TAG, 'get audio manager fail: fail get audioVolumeGroupManager');
return;
}
const maxVolume = await this.audioVolumeGroupManager.getVolume(audio.AudioVolumeType.MEDIA);
const minVolume = await this.audioVolumeGroupManager.getVolume(audio.AudioVolumeType.MEDIA);
const volume = await this.audioVolumeGroupManager.getVolume(audio.AudioVolumeType.MEDIA);
this.localAudioRation = (maxVolume - minVolume) / 100;
this.volume = volume / this.localAudioRation;
console.log(TAG, 'get audio manager done, ' + maxVolume + ', ' + minVolume + ', ' + this.localAudioRation);
}
async setPlayState(state?: number, id?: string, favor?: boolean, elapsedTime?: number) {
if (!this.session) {
console.log(TAG, 'fail set state, session undefined');
promptAction.showToast({ message: 'No Session' });
return null;
}
const params: avSession.AVPlaybackState = {};
if (typeof state !== 'undefined') {
this.playState = state;
params.state = state;
}
if (typeof id !== 'undefined') {
this.isFavorMap[id] = favor;
params.isFavorite = favor;
}
// 更新播放进度
if (elapsedTime !== undefined) {
params.position = {
elapsedTime: elapsedTime,
updateTime: new Date().getTime(),
}
}
this.session?.setAVPlaybackState(params);
console.log(TAG, 'params test ' + JSON.stringify(params));
console.log(TAG, 'isFavorMap test, ' + id + ', ' + JSON.stringify(this.isFavorMap));
return this.session?.setAVPlaybackState(params);
}
async setListenerForMesFromController() {
console.info(TAG, 'setListenerForMesFromController');
this.session?.on('play', () => {
console.info(TAG, 'on play, do play test');
if (this.avPlayer) {
this.avPlayer?.play();
this.setPlayState(avSession.PlaybackState.PLAYBACK_STATE_PLAY);
}
});
this.session?.on('pause', () => {
console.info(TAG, 'on pause, do pause test');
if (this.avPlayer) {
this.avPlayer?.pause();
this.setPlayState(avSession.PlaybackState.PLAYBACK_STATE_PAUSE);
}
});
this.session?.on('stop', () => {
console.info(TAG, 'on stop, do stop test');
if (this.avPlayer) {
this.avPlayer?.stop();
this.setPlayState(avSession.PlaybackState.PLAYBACK_STATE_STOP);
}
});
this.session?.on('playPrevious', () => {
console.info(TAG, 'on playPrevious, do playPrevious test');
this.switchToPreviousByLoopMode();
});
this.session?.on('playNext', () => {
console.info(TAG, 'on playNext, do playNext test');
this.switchToNextByLoopMode();
});
this.session?.on('toggleFavorite', (id) => {
console.info(TAG, 'on toggleFavorite session, do toggleFavorite test: ' + id);
this.setPlayState(undefined, id, !this.isFavorMap[id]);
});
// 注册播放快退命令监听
this.session?.on('rewind', (time?: number) => {
time = time ? time : 0;
let currentTime = this.avPlayer ? this.avPlayer.currentTime : 0;
let timeMs: number = currentTime - time * 1000 <= 0 ? 0 : currentTime - time *1000;
this.avPlayer?.seek(timeMs);
console.info(TAG, 'rewind currentTime ' + timeMs);
this.setPlayState(avSession.PlaybackState.PLAYBACK_STATE_PLAY);
});
// 注册播放快进命令监听
this.session?.on('fastForward', (time?: number) => {
time = time ? time : 0;
let currentTime = this.avPlayer ? this.avPlayer.currentTime : 0;
let timeMs: number = time * 1000 + currentTime > this.duration ? this.duration : time *1000 + currentTime;
if (time * 1000 + currentTime > this.duration) {
this.switchToNextByLoopMode();
} else {
this.avPlayer?.seek(timeMs);
this.setPlayState(avSession.PlaybackState.PLAYBACK_STATE_PLAY);
}
});
this.session?.on('seek', (position) => {
console.info(TAG, 'on seek: seek test: ' + position);
// 修改播放进度
this.avPlayer?.seek(position);
// 重新设置播放进度
const params: avSession.AVPlaybackState = {
position: {
elapsedTime: position,
updateTime: new Date().getTime(),
},
};
this.session?.setAVPlaybackState(params);
});
}
async unregisterSessionListener() {
if (this.session) {
this.session?.off('play');
this.session?.off('pause');
this.session?.off('stop');
this.session?.off('playNext');
this.session?.off('playPrevious');
this.session?.off('seek');
// 主动销毁已创建的session
this.session?.destroy((err) => {
if (err) {
console.info(TAG, `Destroy BusinessError: code: ${err.code}, message: ${err.message}`);
} else {
console.info(TAG, 'Destroy: SUCCESS ');
}
});
}
}
updateVolume(value: number) {
console.info(TAG, 'update volume: ' + this.playType + ', ' + value);
if (this.volume === value) {
console.info(TAG, 'update volume: volume not change');
return;
}
this.volume = value;
if (this.playType === 'cast' && this.castController) {
this.castController?.sendControlCommand({
command: 'setVolume',
parameter: value,
});
}
if (this.playType === 'local' && this.audioManager) {
console.info(TAG, 'update local volume: ' + value);
this.audioManager.setVolume(audio.AudioVolumeType.MEDIA, value * this.localAudioRation);
}
}
async localPlayOrPause() {
console.info(TAG, 'start local play or pause', this.avPlayer?.state);
if (!this.avPlayer) {
console.error(TAG, 'no avplayer');
return;
}
if (this.avPlayer?.state === 'playing') {
console.info(TAG, 'start pause');
await this.avPlayer?.pause();
await this.setPlayState(avSession.PlaybackState.PLAYBACK_STATE_PAUSE);
promptAction.showToast({message: 'pause done'});
} else if (this.avPlayer?.state === 'stopped') {
console.info(TAG, 'start play from stopped');
await this.avPlayer?.prepare();
await this.avPlayer?.play();
await this.setPlayState(avSession.PlaybackState.PLAYBACK_STATE_PLAY);
promptAction.showToast({message: 'play done'});
} else {
console.info(TAG, 'start play from stopped');
await this.avPlayer?.play();
await this.setPlayState(avSession.PlaybackState.PLAYBACK_STATE_PLAY);
promptAction.showToast({message: 'play done'});
console.info(TAG, 'start play done');
}
}
async remotePlayOrPause() {
console.info(TAG, 'start remote play or pause', this.playState);
if (!this.castController) {
console.error(TAG, 'no castController found');
return;
}
if (this.playState === avSession.PlaybackState.PLAYBACK_STATE_INITIAL
|| this.playState === avSession.PlaybackState.PLAYBACK_STATE_PREPARE) {
console.info(TAG, 'start');
let queueItem: avSession.AVQueueItem = {
itemId: 0,
description: this.currentPlayInfo
};
await this.castController?.start(queueItem);
this.playState = avSession.PlaybackState.PLAYBACK_STATE_PLAY;
} else if (this.playState === avSession.PlaybackState.PLAYBACK_STATE_PLAY) {
console.info(TAG, 'pause');
this.castController?.sendControlCommand({
command: 'pause',
})
this.playState = avSession.PlaybackState.PLAYBACK_STATE_PAUSE;
} else {
console.info(TAG, 'play');
this.castController?.sendControlCommand({
command: 'play',
})
this.playState = avSession.PlaybackState.PLAYBACK_STATE_PLAY;
}
}
async autoStartAll(needStart: boolean){
console.info(TAG, 'try auto start all');
console.info(TAG, 'create session');
this.session = await avSession.createAVSession(getContext(), 'audiotestr', 'video');
this.session?.setExtras({
requireAbilityList: ['url-cast'],
});
const params: avSession.AVPlaybackState = {
position: {
elapsedTime: 0,
updateTime: new Date().getTime()
},
};
console.info(TAG, 'try SET SESSION PLAYSTATE');
this.session?.setAVPlaybackState(params);
console.info(TAG, 'create session res: ' + JSON.stringify(this.session));
if (!this.session) {
console.error(TAG, 'fail to create session');
return;
}
console.info(TAG, 'create controller: ' + this.session?.sessionId);
this.controller = await this.session?.getController();
if (!this.controller) {
console.error(TAG, 'fail to create controller');
return;
}
console.info(TAG, 'create controller done: ' + this.controller.sessionId);
console.info(TAG, 'add outputDeviceChange listener');
this.controller.on('outputDeviceChange', async (connectState: avSession.ConnectionState,
device: avSession.OutputDeviceInfo) => {
this.outputDeviceInfo = device;
promptAction.showToast({ message: 'output device changed: ' + connectState });
if (connectState === avSession.ConnectionState.STATE_CONNECTING) {
console.info(TAG, 'connecting');
return;
}
const isPlaying = this.avPlayer && this.avPlayer?.state === 'playing';
console.info(TAG, 'outputDeviceChange res: ' + JSON.stringify(device) + '|' + connectState + ',' + isPlaying);
await this.processDeviceChange(connectState, device);
console.info(TAG, `process Device Change done, ${this.playType}, ${!!this.castController}`);
if (this.playType === 'cast' && this.castController) {
console.info(TAG, 'prepare remote audio info ' + ', ' + isPlaying);
const queueItem: avSession.AVQueueItem = {
itemId: this.currentIndex,
description: this.currentPlayInfo
};
console.info(TAG, `try prepare info, ${JSON.stringify(queueItem)}`);
await this.castController?.prepare(queueItem);
if (isPlaying) {
await this.castController?.start(queueItem);
}
await this.castController?.sendControlCommand({
command: 'setLoopMode',
parameter: this.currentLoopMode,
});
}
console.info(TAG, 'output device change processing finished');
})
console.info(TAG, 'add outputDeviceChange Listener done');
console.info(TAG, 'try prepare local audio: ' + this.session?.sessionId);
this.setListenerForMesFromController();
await this.session?.activate();
await this.setLocalMediaInfo();
await this.setPlayState(avSession.PlaybackState.PLAYBACK_STATE_PAUSE);
if (needStart) {
console.info(TAG, 'start play local');
setTimeout(() => {
this.localPlayOrPause();
}, 100);
}
wantAgent.getWantAgent({
wants: [
{
bundleName: 'com.samples.videoplayer',
abilityName: 'com.samples.videoplayer.EntryAbility'
}
],
operationType: wantAgent.OperationType.START_ABILITIES,
requestCode: 0,
wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
}).then((agent) => {
this.session?.setLaunchAbility(agent);
})
promptAction.showToast({ message: 'auto start done' });
}
async switchToPreviousByLoopMode(){
console.info(TAG, 'switch to previous by loop mode: ' + this.currentLoopMode);
if (this.currentLoopMode === avSession.LoopMode.LOOP_MODE_SINGLE) {
this.playInfoUpdated();
return;
}
if (this.currentLoopMode === avSession.LoopMode.LOOP_MODE_SINGLE) {
const random = Math.floor((Math.random() * 100) + 1);
const target = random % this.songList.length;
if (target === this.currentIndex) {
this.currentIndex = target === 0 ? this.songList.length - 1 : target - 1;
} else {
this.currentIndex = target;
}
this.updateCurrentPlayInfo(this.songList[this.currentIndex], this.audioType);
return;
}
this.currentIndex = this.currentIndex === 0 ? this.songList.length - 1 : this.currentIndex - 1;
this.updateCurrentPlayInfo(this.songList[this.currentIndex], this.audioType);
}
async switchToNextByLoopMode(){
console.info(TAG, 'switch to next by loop mode: ' + this.currentLoopMode);
if (this.currentLoopMode === avSession.LoopMode.LOOP_MODE_SINGLE) {
this.playInfoUpdated();
return;
}
if (this.currentLoopMode === avSession.LoopMode.LOOP_MODE_SHUFFLE) {
const random = Math.floor((Math.random() * 100) + 1);
const target = random % this.songList.length;
if (target === this.currentIndex) {
this.currentIndex = target === this.songList.length - 1 ? 0 : target + 1;
} else {
this.currentIndex = target;
}
this.updateCurrentPlayInfo(this.songList[this.currentIndex], this.audioType);
return;
}
this.currentIndex = this.currentIndex === this.songList.length - 1 ? 0 : this.currentIndex + 1;
this.updateCurrentPlayInfo(this.songList[this.currentIndex], this.audioType);
}
async updateCurrentPlayInfo(item: avSession.AVMediaDescription, audioType: string){
const temp: avSession.AVMediaDescription = {
assetId: item.assetId,
title: item.title,
artist: item.artist,
mediaType: item.mediaType,
mediaSize: item.mediaSize,
startPosition: item.startPosition,
duration: item.duration,
mediaImage: item.mediaImage,
albumTitle: item.albumTitle,
appName: item.appName,
};
if (audioType === 'scan') {
let fd = 0;
await fs.open(item.mediaUri).then(async (file) => {
console.info(TAG, 'fs res: ' + file?.fd);
fd = file?.fd
if (fd != -1 && fd) {
console.info(TAG, 'open fd suc: '+ fd);
temp.fdSrc = {
fd,
};
}
}).catch((err: BusinessError) => {
console.error(TAG, 'start local file cast: ' + JSON.stringify(err));
})
} else {
temp.mediaUri = item.mediaUri;
}
this.currentPlayInfo = temp;
}
async processDeviceChange(connectState: avSession.ConnectionState, device: avSession.OutputDeviceInfo){
if (device?.devices?.[0].castCategory === 0 || connectState === avSession.ConnectionState.STATE_DISCONNECTED) {
this.playType = 'local';
this.playState = avSession.PlaybackState.PLAYBACK_STATE_PAUSE;
if (this.audioVolumeGroupManager) {
const volume = await this.audioVolumeGroupManager.getVolume(audio.AudioVolumeType.MEDIA);
this.volume = volume / this.localAudioRation;
}
await this.setLocalMediaInfo();
return;
}
this.playType = 'cast';
const isRefresh = !!this.castController;
this.castController = await this.session?.getAVCastController();
if (!this.castController) {
console.error(TAG, 'fail to get cast controller');
return;
}
let avPlaybackState = await this.castController?.getAVPlaybackState();
this.playState = avPlaybackState.state || 0;
if (typeof avPlaybackState?.volume !== 'undefined' && avPlaybackState?.volume >= 0) {
this.volume = avPlaybackState?.volume;
}
if (typeof avPlaybackState?.loopMode !== 'undefined') {
this.currentLoopMode = avPlaybackState?.loopMode;
}
console.info(TAG, 'get AVPlaybackState res: ' + JSON.stringify(avPlaybackState) + ', ' + isRefresh);
if (this.avPlayer && this.avPlayer?.state === 'playing') {
console.info(TAG, 'stop avplayer');
this.avPlayer?.stop();
}
console.info(TAG, 'set on playbackStateChange listener: ' + connectState);
this.castController?.on('playbackStateChange', 'all', (state) => {
console.info(TAG, 'play state change: ' + JSON.stringify(state));
if (typeof state?.state !== 'undefined') {
this.playState = state?.state;
}
if (typeof state?.volume !== 'undefined') {
this.volume = state?.volume;
}
if (typeof state?.loopMode !== 'undefined') {
this.currentLoopMode = state?.loopMode;
}
if (typeof state?.extras?.duration !== 'undefined') {
this.duration = state?.extras?.duration as number;
}
if (typeof state?.position?.elapsedTime !== 'undefined' && !this.isProgressSliding) {
this.seedPosition = (state?.position?.elapsedTime / this.duration) * 100;
}
});
this.castController?.on('playPrevious', async (state) => {
console.info(TAG, 'playPrevious: ' + JSON.stringify(state));
this.switchToPreviousByLoopMode()
});
this.castController?.on('playNext', async (state) => {
console.info(TAG, 'playNext: ' + JSON.stringify(state));
this.switchToNextByLoopMode()
});
this.castController?.on('error', (err) => {
console.info(TAG, 'on command error: ' + JSON.stringify(err));
promptAction.showToast({ message: 'error: ' + JSON.stringify(err) });
});
console.info(TAG, 'set on playbackStateChange listener done')
}
build() {
Column() {
Flex({
direction: FlexDirection.Column,
justifyContent: FlexAlign.SpaceBetween,
alignItems: ItemAlign.Center
}) {
// title
Column() {
Flex({
direction: FlexDirection.Row,
justifyContent: FlexAlign.SpaceBetween,
alignItems: ItemAlign.Center
}) {
Column() {
Text($r('app.string.EntryAbility_title'))
.fontWeight(FontWeight.Normal)
.fontSize(24)
.textAlign(TextAlign.Start)
.width("100%")
.fontColor(Color.White)
}
.width('70%')
.height(24)
Button() {
AVCastPicker()
.size({ height: '100%', width: '100%' })
.backgroundColor(Color.White)
.align(Alignment.Center);
}
.width(24)
.height(24)
.backgroundColor(Color.Black)
}
.margin({ left: 24, right: 24, top: 12 })
}
.width('100%')
.backgroundColor(Color.Transparent)
// video
if (this.playType === 'local') {
Row() {
Stack({ alignContent: Alignment.Bottom }) {
XComponent({ id: '', type: 'surface', controller: this.mXComponentController })
.onLoad(() => {
const surfaceId = this.mXComponentController.getXComponentSurfaceId();
console.log(TAG, 'XComponent onLoad, surfaceId = ' + surfaceId);
this.audioUtils.surfaceId = surfaceId;
})
}
.width('100%')
.height(200)
}
.flexShrink(0)
.width('100%')
} else {
Row() {
Stack({ alignContent: Alignment.Center }) {
Text($r('app.string.EntryAbility_sink'))
.fontColor(Color.White)
.fontSize(28)
}
.width('100%')
.height(200)
.backgroundColor(Color.Grey)
}
.flexShrink(0)
.width('100%')
}
// control
Row() {
Flex({
direction: FlexDirection.Column,
justifyContent: FlexAlign.SpaceAround,
alignItems: ItemAlign.Center
}){
Flex({
direction: FlexDirection.Row,
justifyContent: FlexAlign.SpaceEvenly,
alignItems: ItemAlign.Center
})
{
Button() {
Image($r('app.media.music_last'))
.size({ width: '24vp', height: '24vp' })
.fillColor(Color.White)
.backgroundColor(Color.White)
}
.size({
width: '48vp',
height: '48vp'
})
.backgroundColor(Color.Black)
.onClick(() => {
console.info(TAG, 'click play next');
this.switchToPreviousByLoopMode();
})
.key('music_last')
Button() {
Image(this.playState === 2 ? $r('app.media.music_stop') : $r('app.media.music_play'))
.size({ width: '24vp', height: '24vp' })
.fillColor($r('sys.color.ohos_id_color_primary'))
.backgroundColor(Color.White)
}
.size({
width: '48vp',
height: '48vp'
})
.backgroundColor(Color.Transparent)
.onClick(() => {
console.info(TAG, `click play/pause: ${this.playType} ,${this.session}, ${this.controller}`);
if (!this.session && !this.controller) {
this.autoStartAll(true);
} else if (this.playType === 'local') {
this.localPlayOrPause();
} else {
this.remotePlayOrPause();
}
})
.key('music_play_or_pause')
Button() {
Image($r('app.media.music_next'))
.size({ width: '24vp', height: '24vp' })
.fillColor($r('sys.color.ohos_id_color_primary'))
.backgroundColor(Color.White)
}
.size({
width: '48vp',
height: '48vp'
})
.backgroundColor(Color.Transparent)
.onClick(() => {
console.info(TAG, 'click play next');
this.switchToNextByLoopMode();
})
.key('music_next')
}
Flex({
direction: FlexDirection.Row,
justifyContent: FlexAlign.SpaceEvenly,
alignItems: ItemAlign.Center
})
{
Text(`${CommonUtils.millSecond2Minutes(this.seedPosition / 100 * this.duration)}`)
.fontWeight(FontWeight.Normal)
.fontSize(12)
.textAlign(TextAlign.Start)
.fontColor('rgba(255,255,255,0.9)')
Slider({
value: this.seedPosition,
min: 0,
max: 100,
style: SliderStyle.OutSet
})
.trackThickness(this.isProgressSliding ? 8 : 4)
.blockColor('rgba(255,255,255,1)')
.trackColor('rgba(255,255,255,0.3)')
.selectedColor('rgba(255,255,255,0.9)')
.showSteps(false)
.showTips(false)
.onChange((value: number, mode: SliderChangeMode) => {
console.info(TAG, 'value: ' + value + 'mode: ' + mode.toString() )
if (mode === SliderChangeMode.End) {
if (this.playType === 'local') {
this.avPlayer?.seek(value / 100 * this.duration);
const params: avSession.AVPlaybackState = {
position: {
elapsedTime: Math.floor(value / 100 * this.duration),
updateTime: new Date().getTime(),
},
};
this.session?.setAVPlaybackState(params);
console.info(TAG, 'params.position + ' + JSON.stringify((params.position)))
} else {
this.castController?.sendControlCommand({
command: 'seek',
parameter: value / 100 * this.duration,
});
}
}
this.seedPosition = value;
})
.width('70%')
.height(2)
.opacity(1)
.onTouch((event: TouchEvent) => {
console.info(TAG, 'progress touch: ' + event.type)
if (event.type === TouchType.Up) {
this.sliderTimer = setTimeout(() => {
this.isProgressSliding = false;
}, 200);
} else {
clearTimeout(this.sliderTimer);
this.isProgressSliding = true;
}
})
Text(`${CommonUtils.millSecond2Minutes(this.duration)}`)
.fontWeight(FontWeight.Normal)
.fontSize(12)
.textAlign(TextAlign.Start)
.fontColor('rgba(255,255,255,0.9)')
}
.width('100%')
.height(50)
.padding({ left: 10, right: 10 })
}
.padding({ top: 8 })
}
.width('100%')
.height(150)
.padding({ bottom: 20 })
}
}
.width('100%')
.height('100%')
.backgroundColor(Color.Black)
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import avSession from '@ohos.multimedia.avsession';
@Component
export struct SongItem {
@State index: number = -1;
@State item: avSession.AVMediaDescription | null = null;
@Link currentPlayInfo: avSession.AVMediaDescription;
build(){
Flex({
direction: FlexDirection.Row,
justifyContent: FlexAlign.SpaceBetween,
alignItems: ItemAlign.Center
}) {
Flex({
direction: FlexDirection.Row,
justifyContent: FlexAlign.SpaceBetween,
alignItems: ItemAlign.Center
}){
Text('' + (this.index + 1)).fontSize(12)
.fontColor(this.currentPlayInfo.assetId === this.item?.assetId ? '#0A59F7' : Color.Black)
.opacity(0.3)
Flex({
direction: FlexDirection.Row,
justifyContent: FlexAlign.SpaceBetween,
alignItems: ItemAlign.Center
}){
Text(this.item?.title)
.fontSize(14)
.opacity(1)
.maxLines(1)
.fontWeight(FontWeight.Medium)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.fontColor(this.currentPlayInfo.assetId === this.item?.assetId ? '#0A59F7' : Color.Black)
Text(this.item?.artist)
.margin({ top: 8 })
.fontSize(12)
.opacity(1)
.fontWeight(FontWeight.Regular)
.fontColor(this.currentPlayInfo.assetId === this.item?.assetId ? '#0A59F7' : Color.Black)
}.margin({ left: 12 })
}
.margin({ left: 0 })
Image($r('app.media.icon'))
.size({ width: '24vp', height: '24vp' })
.fillColor('#0A59F7')
.backgroundColor(Color.Transparent)
.margin({ left: 4 })
.opacity(1)
.visibility(this.currentPlayInfo.assetId === this.item?.assetId ? Visibility.Visible : Visibility.None)
}
.width('100%')
.height(56)
.padding({ right: 24 })
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"default",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
]
}
}

View File

@ -0,0 +1,8 @@
{
"color": [
{
"name": "start_window_background",
"value": "#FFFFFF"
}
]
}

View File

@ -0,0 +1,24 @@
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
},
{
"name": "EntryAbility_title",
"value": "Video playback"
},
{
"name": "EntryAbility_sink",
"value": "Casting"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

View File

@ -0,0 +1,5 @@
{
"src": [
"pages/Index"
]
}

View File

@ -0,0 +1,24 @@
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
},
{
"name": "EntryAbility_title",
"value": "Video playback"
},
{
"name": "EntryAbility_sink",
"value": "Casting"
}
]
}

View File

@ -0,0 +1,24 @@
{
"string": [
{
"name": "module_desc",
"value": "模块描述"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
},
{
"name": "EntryAbility_title",
"value": "视频播放"
},
{
"name": "EntryAbility_sink",
"value": "投播中"
}
]
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import hilog from '@ohos.hilog';
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
import {Driver, ON, MatchPattern} from '@ohos.UiTest';
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'
const TAG: string = 'SampleText';
const abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
const BUNDLE = 'Sample_';
export default function abilityTest() {
describe('ActsAbilityTest', () => {
/**
* open_sample
*/
it(BUNDLE +'StartAbility_001', 0, async (done: Function) => {
hilog.info(0x0, TAG, 'StartAbility_001 start');
await abilityDelegator.startAbility({
bundleName: 'com.samples.videoplayer',
abilityName: 'EntryAbility'
})
hilog.info(0x0, TAG, 'StartAbility_001 end');
done();
})
/**
* music_last
*/
it(BUNDLE + 'music_last', 0, async (done: Function) => {
hilog.info(0x0, TAG, 'music_last start');
let driver = await Driver.create();
await driver.delayMs(1000);
await driver.assertComponentExist(ON.id('music_last'));
let lastButton = await driver.findComponent(ON.id('music_last'));
await lastButton.click();
await driver.delayMs(1000);
await driver.assertComponentExist(ON.id('music_play_or_pause'));
let playOrPause = await driver.findComponent(ON.id('music_play_or_pause'));
await playOrPause.click();
await driver.delayMs(1000);
await playOrPause.click();
await driver.delayMs(1000);
await driver.assertComponentExist(ON.id('music_next'));
let nextButton = await driver.findComponent(ON.id('music_next'));
await nextButton.click();
await driver.delayMs(1000);
hilog.info(0x0, TAG, 'music_last end');
done();
})
})
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import abilityTest from './Ability.test'
export default function testsuite() {
abilityTest()
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
import hilog from '@ohos.hilog';
import { Hypium } from '@ohos/hypium';
import testsuite from '../test/List.test';
import window from '@ohos.window';
import Want from '@ohos.app.ability.Want';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
export default class TestAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate');
hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? '');
hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:'+ JSON.stringify(launchParam) ?? '');
let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator()
let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs = AbilityDelegatorRegistry.getArguments()
hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!');
Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite)
}
onDestroy() {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage) {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate');
windowStage.loadContent('testability/pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s',JSON.stringify(data) ?? '');
});
}
onWindowStageDestroy() {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy');
}
onForeground() {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground');
}
onBackground() {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground');
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import hilog from '@ohos.hilog';
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
aboutToAppear() {
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility index aboutToAppear');
}
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button() {
Text('next page')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}.type(ButtonType.Capsule)
.margin({ top: 20 })
.backgroundColor('#0D9FFB')
.width('35%')
.height('5%')
.onClick(()=>{
})
}
.width('100%')
}
.height('100%')
}
}

View File

@ -0,0 +1,46 @@
import hilog from '@ohos.hilog';
import TestRunner from '@ohos.application.testRunner';
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
var abilityDelegator = undefined
var abilityDelegatorArguments = undefined
async function onAbilityCreateCallback() {
hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback');
}
async function addAbilityMonitorCallback(err: any) {
hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? '');
}
export default class OpenHarmonyTestRunner implements TestRunner {
constructor() {
}
onPrepare() {
hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare ');
}
async onRun() {
hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run');
abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments()
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator()
const bundleName = abilityDelegatorArguments.bundleName;
const testAbilityName = 'TestAbility';
let lMonitor = {
abilityName: testAbilityName,
onAbilityCreate: onAbilityCreateCallback,
};
abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback)
const want = {
bundleName: bundleName,
abilityName: testAbilityName
};
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
abilityDelegator.startAbility(want, (err : any, data : any) => {
hilog.info(0x0000, 'testTag', 'startAbility : err : %{public}s', JSON.stringify(err) ?? '');
hilog.info(0x0000, 'testTag', 'startAbility : data : %{public}s',JSON.stringify(data) ?? '');
})
hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end');
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"default",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}

View File

@ -0,0 +1,8 @@
{
"color": [
{
"name": "start_window_background",
"value": "#FFFFFF"
}
]
}

View File

@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_test_desc",
"value": "test ability description"
},
{
"name": "TestAbility_desc",
"value": "the test ability"
},
{
"name": "TestAbility_label",
"value": "test label"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -0,0 +1,5 @@
{
"src": [
"testability/pages/Index"
]
}

View File

@ -0,0 +1,18 @@
{
"hvigorVersion": "3.0.9",
"dependencies": {
"@ohos/hvigor-ohos-plugin": "3.0.9"
},
"execution": {
// "daemon": true, /* Enable daemon compilation. Default: true */
// "incremental": true, /* Enable incremental compilation. Default: true */
// "parallel": true, /* Enable parallel compilation. Default: true */
// "typeCheck": false, /* Enable typeCheck. Default: false */
},
"logging": {
// "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
},
"debugging": {
// "stacktrace": false /* Disable stacktrace compilation. Default: false */
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
import { appTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}

View File

@ -0,0 +1,48 @@
#!/bin/bash
# ----------------------------------------------------------------------------
# Hvigor startup script, version 1.0.0
#
# Required ENV vars:
# ------------------
# NODE_HOME - location of a Node home dir
# or
# Add /usr/local/nodejs/bin to the PATH environment variable
# ----------------------------------------------------------------------------
HVIGOR_APP_HOME="`pwd -P`"
HVIGOR_WRAPPER_SCRIPT=${HVIGOR_APP_HOME}/hvigor/hvigor-wrapper.js
warn() {
echo ""
echo -e "\033[1;33m`date '+[%Y-%m-%d %H:%M:%S]'`$@\033[0m"
}
error() {
echo ""
echo -e "\033[1;31m`date '+[%Y-%m-%d %H:%M:%S]'`$@\033[0m"
}
fail() {
error "$@"
exit 1
}
# Determine node to start hvigor wrapper script
if [ -n "${NODE_HOME}" ];then
EXECUTABLE_NODE="${NODE_HOME}/bin/node"
if [ ! -x "$EXECUTABLE_NODE" ];then
fail "ERROR: NODE_HOME is set to an invalid directory,check $NODE_HOME\n\nPlease set NODE_HOME in your environment to the location where your nodejs installed"
fi
else
EXECUTABLE_NODE="node"
which ${EXECUTABLE_NODE} > /dev/null 2>&1 || fail "ERROR: NODE_HOME is not set and not 'node' command found in your path"
fi
# Check hvigor wrapper script
if [ ! -r "$HVIGOR_WRAPPER_SCRIPT" ];then
fail "ERROR: Couldn't find hvigor/hvigor-wrapper.js in ${HVIGOR_APP_HOME}"
fi
# start hvigor-wrapper script
exec "${EXECUTABLE_NODE}" \
"${HVIGOR_WRAPPER_SCRIPT}" "$@"

View File

@ -0,0 +1,64 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Hvigor startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
set WRAPPER_MODULE_PATH=%APP_HOME%\hvigor\hvigor-wrapper.js
set NODE_EXE=node.exe
goto start
:start
@rem Find node.exe
if defined NODE_HOME goto findNodeFromNodeHome
%NODE_EXE% --version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: NODE_HOME is not set and no 'node' command could be found in your PATH.
echo.
echo Please set the NODE_HOME variable in your environment to match the
echo location of your NodeJs installation.
goto fail
:findNodeFromNodeHome
set NODE_HOME=%NODE_HOME:"=%
set NODE_EXE_PATH=%NODE_HOME%/%NODE_EXE%
if exist "%NODE_EXE_PATH%" goto execute
echo.
echo ERROR: NODE_HOME is not set and no 'node' command could be found in your PATH.
echo.
echo Please set the NODE_HOME variable in your environment to match the
echo location of your NodeJs installation.
goto fail
:execute
@rem Execute hvigor
"%NODE_EXE%" "%WRAPPER_MODULE_PATH%" %*
if "%ERRORLEVEL%" == "0" goto hvigorwEnd
:fail
exit /b 1
:hvigorwEnd
if "%OS%" == "Windows_NT" endlocal
:end

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
{
"license": "",
"devDependencies": {
"@ohos/hypium": "1.0.6"
},
"author": "",
"name": "avcastdemo",
"description": "Please describe the basic information.",
"main": "",
"version": "1.0.0",
"dependencies": {}
}

View File

@ -0,0 +1,14 @@
# VideoPlayer 测试用例归档
## 用例表
|测试功能|预置条件|输入|预期输出|测试结果|
|--------------------------------|--------------------------------|--------------------------------|--------------------------------|--------------------------------|
|拉起应用| 设备正常运行| |成功拉起应用|Pass|
|主页信息展示| 位于主页| |展示当前正在播放的视频信息|Pass|
|主页按钮展示| 位于主页| |展示上一个、下一个、播放暂停按钮|Pass|
|主页进度条展示| 位于主页| |展示播放当前位置时间、视频总时间、进度条|Pass|
|播放| 位于主页| 点击播放按钮| 应用进入播放状态|Pass|
|暂停| 位于主页| 点击暂停按钮| 应用进入暂停状态|Pass|
|上一个| 位于主页| 点击上一个按钮| 开始播放上一个视频|Pass|
|下一个| 位于主页| 点击下一个按钮| 开始播放下一个视频|Pass|

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
{
"app": {
"bundleName": "com.samples.camera",
"vendor": "example",
"versionCode": 1000000,
"versionName": "1.0.0",
"icon": "$media:app_icon",
"label": "$string:app_name",
"distributedNotificationEnabled": true
}
}

View File

@ -0,0 +1,8 @@
{
"string": [
{
"name": "app_name",
"value": "CameraSample"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -0,0 +1,140 @@
# CameraSample
### 介绍
本示例主要展示了相机的相关功能,使用[libohcamera.so]
当前版本sample仅用作联调最新sdk未带相机c接口头文件。
接口实现相机的预览、拍照、录像、前后置摄像头切换进行拍照、录像,以及闪光灯、变焦、对焦、曝光等控制类功能。
### 效果预览
| 相机权限 | 麦克风权限 | 文件权限 | 预览界面 |
|----------------------------|----------------------------|----------------------------|---------------------------------|
| ![auth](app_pic/auth1.jpg) | ![auth](app_pic/auth2.jpg) | ![auth](app_pic/auth3.jpg) | ![preview](app_pic/preview.jpg) |
使用说明
(因RK3568设备硬件能力现仅支持验证1、2、3、9、10、13)
1. 弹出是否允许“CameraSample”使用相机点击“允许”
2. 弹出是否允许“CameraSample”使用麦克风点击“允许”
3. 弹出是否允许“CameraSample”访问文件点击“允许”
4. 进入预览界面,预览正常,滑动变焦按钮,同一画面远近变焦效果明显
5. 进入预览界面,预览正常,点击画面模糊处,点击处画面会变得清晰,对焦效果明显
6. 进入预览界面,预览正常,上下滑动屏幕,屏幕场景亮度发生变化,曝光效果明显
7. 进入预览界面,预览正常,点击闪光灯按钮,打开闪光灯,闪光灯正常打开
8. 进入预览界面,预览正常,点击闪光灯按钮,关闭闪光灯,闪光灯关闭
9. 进入预览界面,预览正常,进入拍照模式,点击拍照按钮,拍照正常,左下角会生成照片缩略图,点击左下角缩略图,能够跳转到图库,图片保存正常,打开图片显示正常
10. 进入预览界面,预览正常,切换到录像模式,点击录像,开始录像,再点击停止录像按钮,录像成功,左下角会生成视频缩略图,点击左下角缩略图,能够跳转到图库,录像文件保存正常,播放录像文件正常
11. 进入预览界面,预览正常,切换到前置摄像头,点击拍照按钮,拍照正常,左下角会生成照片缩略图,点击左下角缩略图,能够跳转到图库,图片保存正常,打开图片显示正常
12. 进入预览界面,预览正常,切换到前置摄像头,切换到录像模式,点击录像,开始录像,再点击停止录像按钮,录像成功,左下角会生成视频缩略图,点击左下角缩略图,能够跳转到图库,录像文件保存正常,播放录像文件正常
13. 点击设置按钮,会弹出设置页面。
### 工程目录
```
entry/src/main
|-- cpp
| |-- CMakeLists.txt // Cmake打包配置文件编译工程动态库脚本依赖头文件、cpp以及相关依赖
| |-- camera_manager.cpp // 相机基本功能接口定义cpp实现侧
| |-- camera_manager.h // 相机基本功能接口定义头文件
| |-- main.cpp // NAPI实现JS与C++通信的接口
| |-- types
| `-- libentry
| |-- index.d.ts // 导入NAPI接口供JS调用
| `-- oh-package.json5 // 接口注册配置文件
|-- ets
| |-- Dialog
| | |-- mainDialog.ets // 打开相机APP弹出的网络权限设置
| | `-- settingDialog.ets // 相机APP设置界面布局
| |-- MainAbility
| | `-- MainAbility.ts // 对Ability生命周期管理
| |-- common
| | |-- Constants.ts // 基本参数枚举:纵横比、设备类型、视频帧数
| | |-- DisplayCalculator.ts // 计算界面宽高显示数值
| | |-- settingItem.ets // 设置栏
| | |-- settingPublicLayout.ets // 设置栏公共区域
| | `-- settingRightLayout.ets // 设置栏右边区域
| |-- entryability
| | `-- EntryAbility.ts // Ability的生命周期回调内容
| |-- model
| | |-- DateTimeUtil.ts // 日期工具
| | |-- Logger.ts // 日志工具
| | `-- MediaUtils.ts // 媒体工具
| |-- pages
| | `-- Index.ets // Ability实现的应用的入口页面相机APP首页
| `-- views
| |-- CountdownPage.ets // 倒计时UI页面布局
| |-- FlashingLightPage.ets // 闪光灯UI界面布局
| |-- SlidePage.ets // 滑动滑块UI界面布局
| |-- dividerPage.ets // 分割线UI布局
| |-- focusAreaPage.ets // 对焦区域设置(焦点、侧光点)、单指竖直方向拖动触发曝光补偿设置
| |-- focusPage.ets // 变焦、对焦、曝光、刻度的图标设置、值的设置
| `-- modeSwitchPage.ets // 相机功能模式切换,开启预览、拍照、录像
```
### 具体实现
* 相机功能接口实现在CameraManager.cpp中源码参考[CameraManager.cpp](entry/src/main/cpp/CameraManager.cpp)
* 在NDKCamera构造函数里完成一个相机生命周期初始化的过程包括调用OH_Camera_GetCameraMananger获取CameraMananger调用OH_CameraManager_CreateCaptureSession创建CaptureSession调用CaptureSessionRegisterCallback创建CaptureSession注册回调调用GetSupportedCameras获取支持的camera设备调用GetSupportedOutputCapability获取支持的camera设备能力集调用CreatePreviewOutput创建预览输出调用CreateCameraInput创建相机输入调用CameraInputOpen打开相机输入调用CameraManagerRegisterCallback创建CameraManager注册回调最后调用SessionFlowFn开启Session。
* 其中SessionFlowFn是一个开启预览的动作主要流程包括调用OH_CaptureSession_BeginConfig开始配置会话调用OH_CaptureSession_AddInput把CameraInput加入到会话调用OH_CaptureSession_AddPreviewOutput把previewOutput加入到会话调用OH_CaptureSession_CommitConfig提交配置信息调用OH_CaptureSession_Start开始会话工作还有一步是在开启预览的同时调用IsFocusMode启动对焦功能这边后面会涉及到。
* 在NDKCamera析构函数里完成对相机生命周期释放的过程调用OH_CameraManager_DeleteSupportedCameras删除支持的camera设备调用OH_CameraManager_DeleteSupportedCameraOutputCapability删除支持的camera设备能力集调用OH_Camera_DeleteCameraMananger删除camera manager。
* 拍照功能相关接口封装在StartPhoto接口中主要包含以下流程调用SessionStop关闭session调用SessionBegin做session的一个预置动作调用CreatePhotoOutput创建相机输出调用OH_CaptureSession_AddPhotoOutput将hotoOutput添加至session中调用SessionCommitConfig提交session在调用SessionStart开启session最后调用TakePicture接口开启拍照动作。
* 录像功能相关接口封装在StartVideo接口中主要包含以下流程调用SessionStop关闭session调用SessionBegin做session的一个预置动作调用OH_CaptureSession_RemovePhotoOutput移除相机拍照输出再调用CreatePhotoOutput创建相机输出调用AddPhotoOutput将相机输出添加至session中调用CreateVideoOutput创建录像输出调用AddVideoOutput将录像输出添加至session中然后再调用SessionCommitConfig、SessionStart对session进行提交和开启最后调用VideoOutputRegisterCallback对VideoOutput注册回调。
* 闪光灯功能相关接口封装在HasFlashFn接口中主要包含以下流程调用OH_CaptureSession_HasFlash检测是否支持闪光灯设备再调用OH_CaptureSession_IsFlashModeSupported检测闪光灯模式是否支持然后调用OH_CaptureSession_SetFlashMode设置闪光灯模式最后调用OH_CaptureSession_GetFlashMode获取当前设备的闪光灯模式。
* 变焦功能相关接口封装在setZoomRatioFn接口中主要包含以下流程调用OH_CaptureSession_GetZoomRatioRange获取支持的变焦范围调用OH_CaptureSession_SetZoomRatio设置变焦调用OH_CaptureSession_GetZoomRatio获取当前设备的变焦值。
* 曝光功能相关接口封装在IsExposureModeSupportedFn接口中主要包含以下流程调用OH_CaptureSession_IsExposureModeSupported判断是否支持曝光模式然后调用OH_CaptureSession_SetExposureMode设置曝光模式调用OH_CaptureSession_GetExposureMode获取设置后的曝光模式。调用IsExposureBiasRange接口获取曝光补偿其中包含调用OH_CaptureSession_GetExposureBiasRange获取曝光补偿的范围调用OH_CaptureSession_SetExposureBias设置曝光点调用OH_CaptureSession_GetExposureBias获取曝光点。
* 对焦功能相关接口封装在IsFocusMode接口中主要包含以下流程调用OH_CaptureSession_IsFocusModeSupported判断是否支持对焦模式调用OH_CaptureSession_SetFocusMode设置对焦模式调用OH_CaptureSession_GetFocusMode获取设置后的对焦模式。调用IsFocusPoint接口获取对焦点其中包括调用OH_CaptureSession_SetFocusPoint获取JS侧下发来的对焦点位然后调用OH_CaptureSession_GetFocusPoint获取设置后的对焦点位。
* 视频防抖功能相关接口封装在IsVideoStabilizationModeSupportedFn接口中主要包含以下流程调用OH_CaptureSession_IsVideoStabilizationModeSupported接口查询是否支持指定的视频防抖模式调用OH_CaptureSession_SetVideoStabilizationMode设置视频防抖调用OH_CaptureSession_GetVideoStabilizationMode获取设置后的视频防抖模式。
* 回调接口设置:
* CameraManagerRegisterCallback监听相机状态回调在打开、退出相机相机摄像头切换时会触发
* CameraInputRegisterCallback相机输入发生错误时触发回调
* PreviewOutputRegisterCallback开启预览流时触发回调
* PhotoOutputRegisterCallback开启拍照时触发回调
* VideoOutputRegisterCallback开启录像模式时触发回调
* MetadataOutputRegisterCallback有metadata流输出时触发回调
* CaptureSessionRegisterCallbacksession出现异常时以及开启对焦模式时触发回调
* 相机预览、拍照、录像功能、前后置切换功能实现调用侧位于tableIndex.etsmodeSwitchPage.etsmain.cpp中源码参考[tableIndex.ets](entry/src/main/cpp/ets/pages/tableIndex.ets)[modeSwitchPage.ets](entry/src/main/cpp/ets/views/modeSwitchPage.ets)[main.cpp](entry/src/main/cpp/main.cpp)
* 预览开启预览位于tableIndex.ets下的onPageShow接口其中调用cameraDemo.initCamera接口将预览的surfaceId对焦模式的值以及是前置还是后置摄像头设备作为入参啊传下去实际调用的是main.cpp下的InitCamera接口InitCamera接口将JS侧拿到的参数进行转换再传入cameraManager.cpp中的构造函数里去完成开启相机的操作开启预览并设置好对焦模式。
* 拍照和录像开启拍照位于modeSwitchPage.ets下的isVideoPhotoFn接口通过判断modelBagCol的值是photo还是video将modelBagCol的值videoId拍照的surfaceID或者录像的surfaceId传入接口startPhotoOrVideo。如果是拍照模式则通过modeSwitchPage.ets下的getPhotoSurfaceID接口获取photo surfaceId跳转到main.cpp中的StartPhotoOrVideo接口将传下来的参数进行格式转换再调用CameraManager对象下的StartPhoto接口开启拍照操作如果是录像模式则通过modeSwitchPage.ets下的getVideoSurfaceID接口获取video surfaceId跳转到main.cpp中的StartPhotoOrVideo接口将传下来的参数进行格式转换再调用CameraManager对象下的StartVideo接口开启录像操作
* 前后置切换前后置摄像头切换接口位于modeSwitchPage.ets切换cameraDeviceIndex将先前的session配置释放调用cameraDemo.releaseSession接口实际上是main.cpp下的ReleaseSession接口最终调用到CameraMangaer.cpp下的ReleaseSession接口。然后将预览的surfaceId对焦模式的值以及cameraDeviceIndex传入cameraDemo.initCamera接口中逻辑和预览一致。
* 相机闪光灯、变焦、对焦、曝光功能实现调用侧位于FlashingLightPage.etsSlidePage.etsfocusAreaPage.ets中源码参考[FlashingLightPage.ets](entry/src/main/cpp/ets/views/FlashingLightPage.ets)[SlidePage.ets](entry/src/main/cpp/ets/views/SlidePage.ets)[focusAreaPage.ets](entry/src/main/cpp/ets/views/focusAreaPage.ets)[main.cpp](entry/src/main/cpp/main.cpp)
* 闪光灯闪光灯功能位于FlashingLightPage.etsgetImageDefault接口用作在点击闪光灯图标之后选择闪光灯模式0代表关闭1代表打开2是自动3是常亮。然后在build中通过cameraDemo.hasFlash接口调用到main.cpp中的HasFlash接口最终调到CameraManager.cpp中的HasFlashFn接口完成闪光灯功能的实现。
* 变焦变焦功能位于SlidePage.ets通过调用slideChange接口设置slide滑块的值目前只支持1-6.然后调用cameraDemo.setZoomRatio接口调用到main.cpp中的SetZoomRatio接口最终调到CameraManager.cpp中的setZoomRatioFn接口完成变焦功能的实现。
* 对焦对焦功能位于focusAreaPage.ets通过在build中将对焦焦点下发到cpp侧在CameraManager.cpp文件中的SessionFlowFn函数中会调用IsFocusMode接口来判断是否支持对焦模式然后通过onTouch的方式将对焦点位通过cameraDemo.isFocusPoint接口下发到main.cpp侧的IsFocusPoint接口最终调到CameraManager.cpp中的IsFocusPoint接口。以及调用OH_CaptureSession_SetFocusMode拿到对焦点位来设置对焦模式最后调用OH_CaptureSession_GetFocusMode来获取对焦模式完成对焦功能实现。
* 曝光曝光功能位于focusAreaPage.ets通过在build中将侧光点位下发到cpp侧然后通过onTouch的方式将对焦点位以及侧光点位通过cameraDemo.isFocusPoint接口下发到main.cpp侧的isMeteringPoint接口最终调到CameraManager.cpp中的IsMeteringPoint接口。然后设置曝光补偿单指竖直方向拖动触发该手势事件调用gesture中的cameraDemo.isExposureBiasRange接口将曝光值下发到main.cpp中的IsExposureBiasRange然后经过napi转换后将值传到CameraManager.cpp中的IsExposureBiasRange接口之后从native侧发到曝光补偿的范围再调用OH_CaptureSession_SetExposureBias设置曝光值最后调用OH_CaptureSession_GetExposureBias接口获取曝光值完成曝光功能。
### 相关权限
[ohos.permission.CAMERA](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/permission-list.md)
[ohos.permission.MICROPHONE](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/permission-list.md)
[ohos.permission.READ_MEDIA](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/permission-list.md)
[ohos.permission.WRITE_MEDIA](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/permission-list.md)
### 依赖
不涉及
### 约束与限制
1. 本示例支持标准系统上运行支持设备RK3568
2. 本示例为Stage模型已适配API version 9版本SDKSDK版本号(API Version 9 Release),镜像版本号(4.0Release)
3. 本示例需要使用DevEco Studio 版本号(3.1Release)及以上版本才可编译运行。
### 下载
如需单独下载本工程,执行如下命令:
```
git init
git config core.sparsecheckout true
echo Camera/Camera/ > .git/info/sparse-checkout
git remote add origin https://gitee.com/openharmony/applications_app_samples.git
git pull origin master
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
{
"app": {
"compileSdkVersion": 9,
"compatibleSdkVersion": 9,
"products": [
{
"name": "default",
"signingConfig": "default",
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
}
]
}

View File

@ -0,0 +1,4 @@
/node_modules
/.preview
/build
/.cxx

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
{
"apiType": 'stageMode',
"buildOption": {
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"arguments": "-v",
"abiFilters": [
"armeabi-v7a"
],
"cppFlags": "",
}
},
"targets": [
{
"name": "default",
"runtimeOS": "OpenHarmony"
},
{
"name": "ohosTest",
}
]
}

View File

@ -0,0 +1,2 @@
// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
export { hapTasks } from '@ohos/hvigor-ohos-plugin';

View File

@ -0,0 +1,11 @@
{
"license": "ISC",
"devDependencies": {
"@types/libentry.so": "file:./src/main/cpp/types/libentry"
},
"name": "entry",
"description": "example description",
"repository": {},
"version": "1.0.0",
"dependencies": {}
}

View File

@ -0,0 +1,11 @@
# the minimum version of CMake.
cmake_minimum_required(VERSION 3.4.1)
project(CameraSample)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
add_library(entry SHARED main.cpp camera_manager.cpp)
target_link_libraries(entry PUBLIC libohcamera.so libace_napi.z.so libnative_buffer.so libhilog_ndk.z.so librawfile.z.so)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,152 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CAMERA_NDK_CAMERA_H
#define CAMERA_NDK_CAMERA_H
#include <unistd.h>
#include <string>
#include <thread>
#include <cstdio>
#include <fcntl.h>
#include <map>
#include <string>
#include <vector>
#include <native_buffer/native_buffer.h>
#include "iostream"
#include "mutex"
#include "hilog/log.h"
#include "multimedia/camera_framework/camera.h"
#include "multimedia/camera_framework/camera_input.h"
#include "multimedia/camera_framework/capture_session.h"
#include "multimedia/camera_framework/photo_output.h"
#include "multimedia/camera_framework/preview_output.h"
#include "multimedia/camera_framework/video_output.h"
#include "napi/native_api.h"
#include "multimedia/camera_framework/camera_manager.h"
class NDKCamera {
public:
~NDKCamera();
NDKCamera(char *str, uint32_t focusMode, uint32_t cameraDeviceIndex);
static void Destroy()
{
if (ndkCamera_ != nullptr) {
delete ndkCamera_;
ndkCamera_ = nullptr;
}
}
Camera_ErrorCode CreateCameraInput(void);
Camera_ErrorCode CameraInputOpen(void);
Camera_ErrorCode CameraInputClose(void);
Camera_ErrorCode CameraInputRelease(void);
Camera_ErrorCode GetSupportedCameras(void);
Camera_ErrorCode GetSupportedOutputCapability(void);
Camera_ErrorCode CreatePreviewOutput(void);
Camera_ErrorCode CreatePhotoOutput(char* photoId);
Camera_ErrorCode CreateVideoOutput(char* videoId);
Camera_ErrorCode CreateMetadataOutput(void);
Camera_ErrorCode IsCameraMuted(void);
Camera_ErrorCode PreviewOutputStop(void);
Camera_ErrorCode PreviewOutputRelease(void);
Camera_ErrorCode PhotoOutputRelease(void);
Camera_ErrorCode HasFlashFn(uint32_t mode);
Camera_ErrorCode IsVideoStabilizationModeSupportedFn(uint32_t mode);
Camera_ErrorCode setZoomRatioFn(uint32_t zoomRatio);
Camera_ErrorCode SessionFlowFn(void);
Camera_ErrorCode SessionBegin(void);
Camera_ErrorCode SessionCommitConfig(void);
Camera_ErrorCode SessionStart(void);
Camera_ErrorCode SessionStop(void);
Camera_ErrorCode StartVideo(char* videoId, char* photoId);
Camera_ErrorCode AddVideoOutput(void);
Camera_ErrorCode AddPhotoOutput();
Camera_ErrorCode VideoOutputStart(void);
Camera_ErrorCode StartPhoto(char* mSurfaceId);
Camera_ErrorCode IsExposureModeSupportedFn(uint32_t mode);
Camera_ErrorCode IsMeteringPoint(int x, int y);
Camera_ErrorCode IsExposureBiasRange(int exposureBias);
Camera_ErrorCode IsFocusMode(uint32_t mode);
Camera_ErrorCode IsFocusPoint(float x, float y);
Camera_ErrorCode IsFocusModeSupported(uint32_t mode);
Camera_ErrorCode ReleaseCamera(void);
Camera_ErrorCode SessionRealese(void);
Camera_ErrorCode ReleaseSession(void);
int32_t GetVideoFrameWidth(void);
int32_t GetVideoFrameHeight(void);
int32_t GetVideoFrameRate(void);
Camera_ErrorCode VideoOutputStop(void);
Camera_ErrorCode VideoOutputRelease(void);
Camera_ErrorCode TakePicture(void);
Camera_ErrorCode TakePictureWithPhotoSettings(Camera_PhotoCaptureSetting photoSetting);
// callback
Camera_ErrorCode CameraManagerRegisterCallback(void);
Camera_ErrorCode CameraInputRegisterCallback(void);
Camera_ErrorCode PreviewOutputRegisterCallback(void);
Camera_ErrorCode PhotoOutputRegisterCallback(void);
Camera_ErrorCode VideoOutputRegisterCallback(void);
Camera_ErrorCode MetadataOutputRegisterCallback(void);
Camera_ErrorCode CaptureSessionRegisterCallback(void);
// Get callback
CameraManager_Callbacks* GetCameraManagerListener(void);
CameraInput_Callbacks* GetCameraInputListener(void);
PreviewOutput_Callbacks* GetPreviewOutputListener(void);
PhotoOutput_Callbacks* GetPhotoOutputListener(void);
VideoOutput_Callbacks* GetVideoOutputListener(void);
MetadataOutput_Callbacks* GetMetadataOutputListener(void);
CaptureSession_Callbacks* GetCaptureSessionRegister(void);
private:
NDKCamera(const NDKCamera&) = delete;
NDKCamera& operator = (const NDKCamera&) = delete;
uint32_t cameraDeviceIndex_;
Camera_Manager* cameraManager_;
Camera_CaptureSession* captureSession_;
Camera_Device* cameras_;
uint32_t size_;
Camera_OutputCapability* cameraOutputCapability_;
const Camera_Profile* profile_;
const Camera_VideoProfile* videoProfile_;
Camera_PreviewOutput* previewOutput_;
Camera_PhotoOutput* photoOutput_;
Camera_VideoOutput* videoOutput_;
const Camera_MetadataObjectType* metaDataObjectType_;
Camera_MetadataOutput* metadataOutput_;
Camera_Input* cameraInput_;
bool* isCameraMuted_;
Camera_Position position_;
Camera_Type type_;
char* previewSurfaceId_;
char* photoSurfaceId_;
Camera_ErrorCode ret_;
uint32_t takePictureTimes = 0;
Camera_ExposureMode exposureMode_;
bool isExposureModeSupported_;
bool isFocusModeSupported_;
float minExposureBias_;
float maxExposureBias_;
float step_;
uint32_t focusMode_;
static NDKCamera* ndkCamera_;
static std::mutex mtx_;
volatile bool valid_;
};
#endif // CAMERA_NDK_CAMERA_H

View File

@ -0,0 +1,523 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <js_native_api.h>
#include "camera_manager.h"
#define LOG_TAG "DEMO:"
#define LOG_DOMAIN 0x3200
static NDKCamera* ndkCamera_ = nullptr;
const int32_t ARGS_TWO = 2;
struct Capture_Setting {
int32_t quality;
int32_t rotation;
int32_t location;
bool mirror;
int32_t latitude;
int32_t longitude;
int32_t altitude;
};
static napi_value SetZoomRatio(napi_env env, napi_callback_info info)
{
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_value result;
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_valuetype valuetype0;
napi_typeof(env, args[0], &valuetype0);
int32_t zoomRatio;
napi_get_value_int32(env, args[0], &zoomRatio);
OH_LOG_ERROR(LOG_APP, "SetZoomRatio : %{public}d", zoomRatio);
ndkCamera_->setZoomRatioFn(zoomRatio);
napi_create_int32(env, argc, &result);
return result;
}
static napi_value HasFlash(napi_env env, napi_callback_info info)
{
OH_LOG_ERROR(LOG_APP, "HasFlash");
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_value result;
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_valuetype valuetype0;
napi_typeof(env, args[0], &valuetype0);
int32_t flashMode;
napi_get_value_int32(env, args[0], &flashMode);
OH_LOG_ERROR(LOG_APP, "HasFlash flashMode : %{public}d", flashMode);
ndkCamera_->HasFlashFn(flashMode);
napi_create_int32(env, argc, &result);
return result;
}
static napi_value IsVideoStabilizationModeSupported(napi_env env, napi_callback_info info)
{
OH_LOG_ERROR(LOG_APP, "IsVideoStabilizationModeSupportedFn");
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_value result;
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_valuetype valuetype0;
napi_typeof(env, args[0], &valuetype0);
int32_t videoMode;
napi_get_value_int32(env, args[0], &videoMode);
OH_LOG_ERROR(LOG_APP, "IsVideoStabilizationModeSupportedFn videoMode : %{public}d", videoMode);
ndkCamera_->IsVideoStabilizationModeSupportedFn(videoMode);
napi_create_int32(env, argc, &result);
return result;
}
static napi_value InitCamera(napi_env env, napi_callback_info info)
{
OH_LOG_ERROR(LOG_APP, "InitCamera Start");
size_t requireArgc = 3;
size_t argc = 3;
napi_value args[3] = {nullptr};
napi_value result;
size_t typeLen = 0;
char* surfaceId = nullptr;
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_get_value_string_utf8(env, args[0], nullptr, 0, &typeLen);
surfaceId = new char[typeLen + 1];
napi_get_value_string_utf8(env, args[0], surfaceId, typeLen + 1, &typeLen);
napi_valuetype valuetype1;
napi_typeof(env, args[1], &valuetype1);
int32_t focusMode;
napi_get_value_int32(env, args[1], &focusMode);
uint32_t cameraDeviceIndex;
napi_get_value_uint32(env, args[ARGS_TWO], &cameraDeviceIndex);
OH_LOG_ERROR(LOG_APP, "InitCamera focusMode : %{public}d", focusMode);
OH_LOG_ERROR(LOG_APP, "InitCamera surfaceId : %{public}s", surfaceId);
OH_LOG_ERROR(LOG_APP, "InitCamera cameraDeviceIndex : %{public}d", cameraDeviceIndex);
if (ndkCamera_) {
OH_LOG_ERROR(LOG_APP, "ndkCamera_ is not null");
delete ndkCamera_;
ndkCamera_ = nullptr;
}
ndkCamera_ = new NDKCamera(surfaceId, focusMode, cameraDeviceIndex);
OH_LOG_ERROR(LOG_APP, "InitCamera End");
napi_create_int32(env, argc, &result);
return result;
}
static napi_value ReleaseCamera(napi_env env, napi_callback_info info)
{
OH_LOG_ERROR(LOG_APP, "ReleaseCamera Start");
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_value result;
size_t typeLen = 0;
char* surfaceId = nullptr;
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
ndkCamera_->ReleaseCamera();
if (ndkCamera_) {
OH_LOG_ERROR(LOG_APP, "ndkCamera_ is not null");
delete ndkCamera_;
ndkCamera_ = nullptr;
}
OH_LOG_ERROR(LOG_APP, "ReleaseCamera End");
napi_create_int32(env, argc, &result);
return result;
}
static napi_value ReleaseSession(napi_env env, napi_callback_info info)
{
OH_LOG_ERROR(LOG_APP, "ReleaseCamera Start");
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_value result;
size_t typeLen = 0;
char* surfaceId = nullptr;
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
ndkCamera_->ReleaseSession();
OH_LOG_ERROR(LOG_APP, "ReleaseCamera End");
napi_create_int32(env, argc, &result);
return result;
}
static napi_value StartPhotoOrVideo(napi_env env, napi_callback_info info)
{
OH_LOG_INFO(LOG_APP, "StartPhotoOrVideo Start");
Camera_ErrorCode ret = CAMERA_OK;
size_t requireArgc = 3;
size_t argc = 3;
napi_value args[3] = {nullptr};
napi_value result;
size_t typeLen = 0;
size_t videoIdLen = 0;
size_t photoIdLen = 0;
char* modeFlag = nullptr;
char* videoId = nullptr;
char* photoId = nullptr;
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_get_value_string_utf8(env, args[0], nullptr, 0, &typeLen);
modeFlag = new char[typeLen + 1];
napi_get_value_string_utf8(env, args[0], modeFlag, typeLen + 1, &typeLen);
napi_get_value_string_utf8(env, args[1], nullptr, 0, &videoIdLen);
videoId = new char[videoIdLen + 1];
napi_get_value_string_utf8(env, args[1], videoId, videoIdLen + 1, &videoIdLen);
napi_get_value_string_utf8(env, args[ARGS_TWO], nullptr, 0, &photoIdLen);
photoId = new char[photoIdLen + 1];
napi_get_value_string_utf8(env, args[ARGS_TWO], photoId, photoIdLen + 1, &photoIdLen);
if (!strcmp(modeFlag, "photo")) {
OH_LOG_ERROR(LOG_APP, "StartPhoto surfaceId %{public}s", photoId);
ret = ndkCamera_->StartPhoto(photoId);
} else if (!strcmp(modeFlag, "video")) {
ret = ndkCamera_->StartVideo(videoId, photoId);
OH_LOG_ERROR(LOG_APP, "StartPhotoOrVideo %{public}s, %{public}s", videoId, photoId);
}
napi_create_int32(env, ret, &result);
return result;
}
static napi_value VideoOutputStart(napi_env env, napi_callback_info info)
{
OH_LOG_INFO(LOG_APP, "VideoOutputStart Start");
napi_value result;
Camera_ErrorCode ret = ndkCamera_->VideoOutputStart();
napi_create_int32(env, ret, &result);
return result;
}
static napi_value IsExposureModeSupported(napi_env env, napi_callback_info info)
{
OH_LOG_INFO(LOG_APP, "IsExposureModeSupported exposureMode start.");
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_value result;
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_valuetype valuetype0;
napi_typeof(env, args[0], &valuetype0);
int32_t exposureMode;
napi_get_value_int32(env, args[0], &exposureMode);
OH_LOG_ERROR(LOG_APP, "IsExposureModeSupported exposureMode : %{public}d", exposureMode);
ndkCamera_->IsExposureModeSupportedFn(exposureMode);
OH_LOG_INFO(LOG_APP, "IsExposureModeSupported exposureMode end.");
napi_create_int32(env, argc, &result);
return result;
}
static napi_value IsMeteringPoint(napi_env env, napi_callback_info info)
{
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_value result;
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_valuetype valuetype0;
napi_typeof(env, args[0], &valuetype0);
int x;
napi_get_value_int32(env, args[0], &x);
napi_valuetype valuetype1;
napi_typeof(env, args[0], &valuetype0);
int y;
napi_get_value_int32(env, args[1], &y);
ndkCamera_->IsMeteringPoint(x, y);
napi_create_int32(env, argc, &result);
return result;
}
static napi_value IsExposureBiasRange(napi_env env, napi_callback_info info)
{
OH_LOG_INFO(LOG_APP, "IsExposureBiasRange start.");
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_value result;
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_valuetype valuetype0;
napi_typeof(env, args[0], &valuetype0);
int exposureBiasValue;
napi_get_value_int32(env, args[0], &exposureBiasValue);
ndkCamera_->IsExposureBiasRange(exposureBiasValue);
OH_LOG_INFO(LOG_APP, "IsExposureBiasRange end.");
napi_create_int32(env, argc, &result);
return result;
}
static napi_value IsFocusModeSupported(napi_env env, napi_callback_info info)
{
OH_LOG_INFO(LOG_APP, "IsFocusModeSupported start.");
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_value result;
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_valuetype valuetype0;
napi_typeof(env, args[0], &valuetype0);
int32_t focusMode;
napi_get_value_int32(env, args[0], &focusMode);
OH_LOG_ERROR(LOG_APP, "IsFocusModeSupportedFn videoMode : %{public}d", focusMode);
ndkCamera_->IsFocusModeSupported(focusMode);
OH_LOG_INFO(LOG_APP, "IsFocusModeSupported end.");
napi_create_int32(env, argc, &result);
return result;
}
static napi_value IsFocusPoint(napi_env env, napi_callback_info info)
{
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_value result;
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_valuetype valuetype0;
napi_typeof(env, args[0], &valuetype0);
double x;
napi_get_value_double(env, args[0], &x);
napi_valuetype valuetype1;
napi_typeof(env, args[1], &valuetype1);
double y;
napi_get_value_double(env, args[1], &y);
float focusPointX = static_cast<float>(x);
float focusPointY = static_cast<float>(y);
ndkCamera_->IsFocusPoint(focusPointX, focusPointY);
napi_create_int32(env, argc, &result);
return result;
}
static napi_value GetVideoFrameWidth(napi_env env, napi_callback_info info)
{
OH_LOG_ERROR(LOG_APP, "GetVideoFrameWidth Start");
size_t argc = 1;
napi_value args[1] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_value result = nullptr;
napi_create_int32(env, ndkCamera_->GetVideoFrameWidth(), &result);
OH_LOG_ERROR(LOG_APP, "GetVideoFrameWidth End");
return result;
}
static napi_value GetVideoFrameHeight(napi_env env, napi_callback_info info)
{
OH_LOG_ERROR(LOG_APP, "GetVideoFrameHeight Start");
size_t argc = 1;
napi_value args[1] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_value result = nullptr;
napi_create_int32(env, ndkCamera_->GetVideoFrameHeight(), &result);
OH_LOG_ERROR(LOG_APP, "GetVideoFrameHeight End");
return result;
}
static napi_value GetVideoFrameRate(napi_env env, napi_callback_info info)
{
OH_LOG_ERROR(LOG_APP, "GetVideoFrameRate Start");
size_t argc = 1;
napi_value args[1] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_value result = nullptr;
napi_create_int32(env, ndkCamera_->GetVideoFrameRate(), &result);
OH_LOG_ERROR(LOG_APP, "GetVideoFrameRate End");
return result;
}
static napi_value VideoOutputStopAndRelease(napi_env env, napi_callback_info info)
{
OH_LOG_ERROR(LOG_APP, "VideoOutputStopAndRelease Start");
size_t argc = 1;
napi_value args[1] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_value result = nullptr;
ndkCamera_->VideoOutputStop();
ndkCamera_->VideoOutputRelease();
OH_LOG_ERROR(LOG_APP, "VideoOutputStopAndRelease End");
napi_create_int32(env, argc, &result);
return result;
}
static napi_value TakePicture(napi_env env, napi_callback_info info)
{
OH_LOG_INFO(LOG_APP, "TakePicture Start");
napi_value result;
Camera_ErrorCode ret = ndkCamera_->TakePicture();
OH_LOG_ERROR(LOG_APP, "TakePicture result is %{public}d", ret);
napi_create_int32(env, ret, &result);
return result;
}
static napi_value GetCaptureParam(napi_env env, napi_value captureConfigValue, Capture_Setting *config)
{
napi_value value = nullptr;
napi_get_named_property(env, captureConfigValue, "quality", &value);
napi_get_value_int32(env, value, &config->quality);
napi_get_named_property(env, captureConfigValue, "rotation", &value);
napi_get_value_int32(env, value, &config->rotation);
napi_get_named_property(env, captureConfigValue, "mirror", &value);
napi_get_value_bool(env, value, &config->mirror);
napi_get_named_property(env, captureConfigValue, "latitude", &value);
napi_get_value_int32(env, value, &config->latitude);
napi_get_named_property(env, captureConfigValue, "longitude", &value);
napi_get_value_int32(env, value, &config->longitude);
napi_get_named_property(env, captureConfigValue, "altitude", &value);
napi_get_value_int32(env, value, &config->altitude);
OH_LOG_INFO(LOG_APP, "get quality %{public}d, rotation %{public}d, mirror %{public}d, latitude "
"%{public}d, longitude %{public}d, altitude %{public}d", config->quality, config->rotation,
config->mirror, config->latitude, config->longitude, config->altitude);
return 0;
}
static void SetConfig(Capture_Setting settings, Camera_PhotoCaptureSetting* photoSetting, Camera_Location* location)
{
if (photoSetting == nullptr || location == nullptr) {
OH_LOG_INFO(LOG_APP, "photoSetting is null");
}
photoSetting->quality = static_cast<Camera_QualityLevel>(settings.quality);
photoSetting->rotation = static_cast<Camera_ImageRotation>(settings.rotation);
photoSetting->mirror = settings.mirror;
location->altitude = settings.altitude;
location->latitude = settings.latitude;
location->longitude = settings.longitude;
photoSetting->location = location;
}
static napi_value TakePictureWithSettings(napi_env env, napi_callback_info info)
{
OH_LOG_INFO(LOG_APP, "TakePictureWithSettings Start");
size_t requireArgc = 1;
size_t argc = 1;
napi_value args[1] = {nullptr};
Camera_PhotoCaptureSetting photoSetting;
Capture_Setting setting_inner;
Camera_Location* location = new Camera_Location;
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
GetCaptureParam(env, args[0], &setting_inner);
SetConfig(setting_inner, &photoSetting, location);
napi_value result;
Camera_ErrorCode ret = ndkCamera_->TakePictureWithPhotoSettings(photoSetting);
OH_LOG_ERROR(LOG_APP, "TakePictureWithSettings result is %{public}d", ret);
napi_create_int32(env, ret, &result);
return result;
}
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
napi_property_descriptor desc[] = {
{ "initCamera", nullptr, InitCamera, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "startPhotoOrVideo", nullptr, StartPhotoOrVideo, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "videoOutputStart", nullptr, VideoOutputStart, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "setZoomRatio", nullptr, SetZoomRatio, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "hasFlash", nullptr, HasFlash, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "isVideoStabilizationModeSupported", nullptr, IsVideoStabilizationModeSupported,
nullptr, nullptr, nullptr, napi_default, nullptr },
{ "isExposureModeSupported", nullptr, IsExposureModeSupported,
nullptr, nullptr, nullptr, napi_default, nullptr },
{ "isMeteringPoint", nullptr, IsMeteringPoint, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "isExposureBiasRange", nullptr, IsExposureBiasRange, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "IsFocusModeSupported", nullptr, IsFocusModeSupported, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "isFocusPoint", nullptr, IsFocusPoint, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "getVideoFrameWidth", nullptr, GetVideoFrameWidth, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "getVideoFrameHeight", nullptr, GetVideoFrameHeight, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "getVideoFrameRate", nullptr, GetVideoFrameRate, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "videoOutputStopAndRelease", nullptr, VideoOutputStopAndRelease,
nullptr, nullptr, nullptr, napi_default, nullptr },
{ "takePicture", nullptr, TakePicture, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "takePictureWithSettings", nullptr, TakePictureWithSettings, nullptr, nullptr, nullptr,
napi_default, nullptr },
{ "releaseSession", nullptr, ReleaseSession, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "releaseCamera", nullptr, ReleaseCamera, nullptr, nullptr, nullptr, napi_default, nullptr }
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
static napi_module demoModule = {
.nm_version =1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "entry",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
napi_module_register(&demoModule);
}

View File

@ -0,0 +1,29 @@
export const initCamera:(surfaceId: string, focusMode: number, cameraDeviceIndex: number) => number;
export const startPhotoOrVideo: (modeFlag: string, videoId: string, photoId: string) => number;
export const videoOutputStart: () => number;
export const setZoomRatio: (a: number) => number;
export const takePicture: () => number;
export const takePictureWithSettings: (setting: Capture_Setting) => number;
export const hasFlash: (a: number) => number;
export const isVideoStabilizationModeSupported: (a: number) => number;
export const isExposureModeSupported:(a: number) => number;
export const isMeteringPoint: (a: number, b: number) => number;
export const isExposureBiasRange: (a: number) => number;
export const isFocusModeSupported: (a: number) => number;
export const isFocusPoint: (a: number, b: number) => number;
export const getVideoFrameWidth: () => number;
export const getVideoFrameHeight: () => number;
export const getVideoFrameRate: () => number;
export const videoOutputStopAndRelease: () => number;
export const releaseCamera: () => number;
export const releaseSession: () => number;
interface Capture_Setting {
quality: number;
rotation: number;
mirror: boolean;
latitude: number;
longitude: number;
altitude: number;
}

View File

@ -0,0 +1,6 @@
{
"types": "./index.d.ts",
"name": "libentry.so",
"description": "",
"version": ""
}

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@CustomDialog
export struct mainDialog {
controller: CustomDialogController;
async aboutToAppear() {
console.info(`Camera aboutToAppear entry`);
}
build() {
Column() {
Column() {
Text('允许“相机”使用无线数据?')
.fontSize(16)
.fontFamily('HarmonyHeiTi')
.fontColor('#182431')
.width('100%')
.fontWeight(500)
.textAlign(TextAlign.Start)
Text('关闭无线数据时,部分功能可能无法使用。')
.fontSize(10)
.opacity(0.6)
.textAlign(TextAlign.Start)
.width('100%')
.fontFamily('HarmonyHeiTi')
.fontColor('#182431')
.fontWeight(400)
.margin({ top: 2 })
}
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceEvenly, alignItems: ItemAlign.Auto }) {
Button() {
Text('无线局域网与蜂窝网络')
.fontColor($r('app.color.theme_color'))
.fontSize(14)
.width('100%')
.fontWeight(500)
.height('30%')
.textAlign(TextAlign.Center)
}
.backgroundColor('#FFF')
.onClick(() => {
this.controller.close();
})
Button() {
Text('仅限无限局域网')
.fontColor($r('app.color.theme_color'))
.fontSize(14)
.fontWeight(500)
.width('100%')
.height('30%')
.textAlign(TextAlign.Center)
}
.backgroundColor('#FFF')
.onClick(() => {
this.controller.close();
})
Button() {
Text('不允许')
.fontColor($r('app.color.theme_color'))
.fontSize(14)
.fontWeight(500)
.width('100%')
.height('30%')
.textAlign(TextAlign.Center)
}
.backgroundColor('#FFF')
.onClick(() => {
this.controller.close();
})
}
.margin({ top: 10 })
.height('80%')
.backgroundColor('#FFF')
}
.width('750px')
.height('700px')
.padding({
top: '3%',
left: '2%',
right: '2%',
bottom: '2%'
})
.borderRadius(24)
.backgroundColor('#FFF')
}
}

View File

@ -0,0 +1,209 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { settingPublicLayout } from '../common/SettingPublicLayout';
import { settingRightLayout } from '../common/SettingRightLayout';
@CustomDialog
export struct settingDialog {
private controller: CustomDialogController;
@Prop surfaceId: string;
@Prop cameraDeviceIndex: number;
@Link referenceLineBol: boolean;
// Index of a certain setting clicked on
@State leftSliderIndex: number = 1;
// Mirror persistence, enter again, confirm if the switch is turned on
getMirrorBol(bol) {
globalThis.settingDataObj.mirrorBol = bol;
}
// Persistent geographical location, re-enter to determine if the switch is turned on
getLocationBol(bol) {
globalThis.settingDataObj.locationBol = bol;
}
// Persist the reference line, enter again to determine if the switch is turned on
getReferenceLineBol(bol) {
globalThis.settingDataObj.referenceLineBol = bol;
this.referenceLineBol = bol;
}
onPageShow() {
console.info('globalThis onPageShow:' + JSON.stringify(globalThis.settingDataObj));
}
onPageHide() {
console.info('globalThis onPageHide:' + JSON.stringify(globalThis.settingDataObj));
}
build() {
Column() {
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
Column() {
Row() {
Text('设置')
.fontSize(26)
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Start)
.width('96%')
.onClick(() => {
this.controller.close()
})
}.margin({ top: '100px', bottom: '20px' })
settingPublicLayout({
icon: $r('app.media.ic_camera_set__mirror'),
isModeBol: true,
borderBol: false,
iconModeBol: true,
modeMessage: '自拍镜像',
backNum: 1,
leftSliderIndex: $leftSliderIndex,
setModeBol: globalThis.settingDataObj.mirrorBol,
getModeBol: this.getMirrorBol.bind(this)
})
Column() {
settingPublicLayout({
icon: $r('app.media.ic_camera_set__antishake'),
isModeBol: true,
borderBol: true,
borderBole: true,
backNum: 2,
leftSliderIndex: $leftSliderIndex,
modeMessage: '视频防抖'
})
Divider().width(420).margin({ left: 20 })
settingPublicLayout({
backNum: 3,
leftSliderIndex: $leftSliderIndex,
icon: $r('app.media.ic_camera_set_exposure'),
modeMessage: '曝光模式'
})
Divider().width(420).margin({ left: 20 })
settingPublicLayout({
backNum: 4,
leftSliderIndex: $leftSliderIndex,
icon: $r('app.media.ic_camera_set_af'),
modeMessage: '对焦模式'
})
Divider().width(420).margin({ left: 20 })
settingPublicLayout({
backNum: 5,
leftSliderIndex: $leftSliderIndex,
icon: $r('app.media.ic_camera_set_quality'),
modeMessage: '拍摄质量'
})
Divider().width(420).margin({ left: 20 })
settingPublicLayout({
backNum: 6,
leftSliderIndex: $leftSliderIndex,
icon: $r('app.media.ic_camera_set_location'),
isModeBol: true,
borderBol: true,
borderBole: false,
iconModeBol: true,
modeMessage: '拍摄时显示地理位置',
setModeBol: globalThis.settingDataObj.locationBol,
getModeBol: this.getLocationBol.bind(this)
})
}
.backgroundColor(Color.White)
.borderRadius(16)
.margin({ top: 15 })
Column() {
settingPublicLayout({
backNum: 7,
leftSliderIndex: $leftSliderIndex,
icon: $r('app.media.ic_camera_set_format'),
modeMessage: '照片格式',
isModeBol: true,
borderBol: true,
borderBole: true,
})
Divider().width(420).margin({ left: 20 })
settingPublicLayout({
backNum: 8,
leftSliderIndex: $leftSliderIndex,
icon: $r('app.media.ic_camera_set_class'),
modeMessage: '照片方向配置'
})
Divider().width(420).margin({ left: 20 })
settingPublicLayout({
backNum: 9,
leftSliderIndex: $leftSliderIndex,
icon: $r('app.media.ic_camera_set_pic_resolution'),
modeMessage: '照片分辨率'
})
Divider().width(420).margin({ left: 20 })
settingPublicLayout({
backNum: 10,
leftSliderIndex: $leftSliderIndex,
icon: $r('app.media.ic_camera_set_video_resolution'),
modeMessage: '视频分辨率'
})
Divider().width(420).margin({ left: 20 })
settingPublicLayout({
backNum: 11,
leftSliderIndex: $leftSliderIndex,
icon: $r('app.media.ic_camera_set_video_rate'),
modeMessage: '录像帧率',
isModeBol: true,
borderBol: true,
borderBole: false,
})
}
.backgroundColor(Color.White)
.borderRadius(16)
.margin({ top: 15 })
settingPublicLayout({
backNum: 12,
leftSliderIndex: $leftSliderIndex,
icon: $r('app.media.ic_camera_set_line'),
modeMessage: '参考线',
isModeBol: true,
borderBol: false,
iconModeBol: true,
setModeBol: globalThis.settingDataObj.referenceLineBol,
getModeBol: this.getReferenceLineBol.bind(this)
})
.margin({ top: 15 })
}
.width('38%')
// Set Right Selection List
Column() {
settingRightLayout({ settingMessageNum: this.leftSliderIndex });
}
.width('58%')
}
.height('100%')
.width('96%')
}
// page display
.onAppear(() => {
})
// Page disappears
.onDisAppear(async () => {
console.info('globalThis onPageHide:' + JSON.stringify(globalThis.settingDataObj));
})
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import hilog from '@ohos.hilog';
import Ability from '@ohos.app.ability.UIAbility'
import Window from '@ohos.window'
import deviceInfo from '@ohos.deviceInfo'
export default class MainAbility extends Ability {
onCreate(want, launchParam) {
hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? '');
hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:' + JSON.stringify(launchParam) ?? '');
globalThis.abilityContext = this.context
}
onDestroy() {
hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: Window.WindowStage) {
// Main window is created, set main page for this ability
hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.getMainWindow().then((win) => {
win.setLayoutFullScreen(true).then(() => {
win.setSystemBarEnable(['navigation']).then(() => {
})
})
win.setSystemBarProperties({
navigationBarColor: '#00000000',
navigationBarContentColor: '#B3B3B3'
}).then(() => {
})
})
this.onLoadContent(windowStage, 'pages/Index');
}
onLoadContent(windowStage, page) {
windowStage.loadContent(page, (err, data) => {
if (err.code) {
hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.ERROR);
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
onWindowStageDestroy() {
// Main window is destroyed, release UI related resources
hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground() {
// Ability has brought to foreground
hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground() {
// Ability has back to background
hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export class Constants {
// aspect ratio: width/height
static readonly MIN_ASPECT_RATIO = 4 / 3;
static readonly MAX_ASPECT_RATIO = 16 / 9;
static readonly VIDEO_MAX_WIDTH = 2048;
static readonly PHOTO_MAX_WIDTH = 2048;
static readonly SURFACE_BOTTOM_MARGIN = 50;
// device type
static readonly TABLET = 'tablet';
static readonly DEFAULT = 'default';
static readonly PHONE = 'phone';
// video frame
static readonly VIDEO_FRAME_30 = 30;
static readonly VIDEO_FRAME_15 = 15;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Constants } from '../common/Constants';
export default class DisplayCalculator {
public static calcSurfaceDisplaySize(screenWidth: number, screenHeight: number, defaultAspectRatio: number): {
width: number,
height: number
} {
const displaySize = {
width: 1920, height: 1080
};
// @ts-ignore
if (AppStorage.get<string>('deviceType') === Constants.TABLET || screenWidth > screenHeight) {
if (screenWidth / screenHeight > defaultAspectRatio) {
displaySize.width = Math.floor(screenHeight * defaultAspectRatio);
displaySize.height = Math.floor(screenHeight);
} else {
displaySize.width = Math.floor(screenWidth);
displaySize.height = Math.floor(screenWidth / defaultAspectRatio);
}
} else {
if (screenWidth / screenHeight > defaultAspectRatio) {
displaySize.width = Math.floor(screenHeight / defaultAspectRatio);
displaySize.height = Math.floor(screenHeight);
} else {
displaySize.width = Math.floor(screenWidth);
displaySize.height = Math.floor(screenWidth * defaultAspectRatio);
}
}
return displaySize;
}
}

View File

@ -0,0 +1,107 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import cameraDemo from 'libentry.so';
@Component
export struct settingItem {
private itemData: string; // The name of the selected mode
@Prop settingMessageNum: number; // Incoming click settings
private index: number; // Index value of the selected mode
@Link @Watch('onChangeFn') isIndex: number; // Which icon is selected by default
@State isBol: boolean = false; // Hide Display Icons
// Corresponding to the selected setting parameters in the click mode setting
selectMode() {
switch (this.settingMessageNum) {
case 2:
globalThis.settingDataObj.videoStabilizationMode = this.index;
cameraDemo.isVideoStabilizationModeSupported(globalThis.settingDataObj.videoStabilizationMode);
break
case 3:
globalThis.settingDataObj.exposureMode = this.index;
cameraDemo.isExposureModeSupported(globalThis.settingDataObj.exposureMode);
break;
case 4:
globalThis.settingDataObj.focusMode = this.index;
cameraDemo.isFocusModeSupported(globalThis.settingDataObj.focusMode);
break;
case 5:
globalThis.settingDataObj.photoQuality = this.index;
break;
case 7:
globalThis.settingDataObj.photoFormat = this.index;
break;
case 8:
globalThis.settingDataObj.photoOrientation = this.index;
break;
case 9:
// Photo resolution
globalThis.settingDataObj.photoResolution = this.index;
let ind = this.itemData.indexOf('*');
globalThis.photoResolutionWidth = Number(this.itemData.substring(0, ind));
globalThis.photoResolutionHeight = Number(this.itemData.substring(ind + 1));
break;
case 10:
// Video resolution
globalThis.settingDataObj.videoResolution = this.index;
let index = this.itemData.indexOf('*');
globalThis.videoResolutionWidth = Number(this.itemData.substring(0, index));
globalThis.videoResolutionHeight = Number(this.itemData.substring(index + 1));
break;
case 11:
globalThis.settingDataObj.videoFrame = this.index;
globalThis.videoFrame = this.itemData;
break;
}
}
onChangeFn() {
if (this.index === this.isIndex) {
this.isBol = true;
} else {
this.isBol = false;
}
}
aboutToAppear() {
this.onChangeFn();
}
build() {
Column() {
Row() {
Text(this.itemData)
.fontColor('#182431')
.fontSize(16)
.fontWeight(600)
.textAlign(TextAlign.Start)
.width('90%')
if (this.isBol) {
Image($r('app.media.ic_camera_set_checked')).width(24).height(24);
} else {
Image('').width(24).height(24).backgroundColor(Color.White);
}
}
.justifyContent(FlexAlign.SpaceBetween)
.height(65)
.onClick(() => {
this.isIndex = this.index;
this.selectMode();
})
Divider().width(680).margin({ left: 10 })
}
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@Component
export struct settingPublicLayout {
private icon: Resource;
private modeMessage: string;
private getModeBol: (mirrorBol: boolean) => void;
private setModeBol: boolean;
private isModeBol: boolean = false;
private borderBol: boolean = false;
private borderBole: boolean = false;
private iconModeBol: boolean = false;
private backNum: number = 1;
@State backClBol: boolean = false;
@Link @Watch('leftSliderChange') leftSliderIndex: number;
leftSliderChange() {
if (this.backNum == this.leftSliderIndex) {
this.backClBol = true;
} else {
this.backClBol = false;
}
}
aboutToAppear() {
this.leftSliderChange();
}
isBorderFn() {
if (this.isModeBol) {
if (this.borderBol) {
if (this.borderBole) {
return { topLeft: 16, topRight: 16 };
} else {
return { bottomLeft: 16, bottomRight: 16 };
}
} else {
return { topLeft: 16, topRight: 16, bottomLeft: 16, bottomRight: 16 };
}
} else {
return {};
}
}
build() {
Row() {
Row() {
Row() {
Image(this.icon).width(20).height(20).margin({ left: '15px' });
Text(this.modeMessage).margin({ left: '20px' })
}
if (this.iconModeBol) {
Toggle({ type: ToggleType.Switch, isOn: this.setModeBol })
.selectedColor($r('app.color.theme_color'))
.switchPointColor('#FFFFFF')
.onChange((isOn: boolean) => {
this.getModeBol(isOn);
console.info('Component status:' + isOn)
})
} else {
Image($r('app.media.ic_camera_set_arrow')).width(20).height(20).margin({ right: 10 })
}
}
.onClick(() => {
this.leftSliderIndex = this.backNum;
console.info('leftSliderIndex status:' + this.leftSliderIndex);
console.info('backNum status:' + this.backNum);
})
.justifyContent(FlexAlign.SpaceBetween)
.width('100%')
.borderRadius(15)
.height('90px')
.backgroundColor(this.backClBol ? 'rgba(255,0,52,0.10)' : '')
}
.padding(2)
.width('100%')
.height('100px')
.borderRadius(this.isBorderFn())
.backgroundColor(Color.White)
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { settingItem } from './SettingItem'
@Component
export struct settingRightLayout {
@Prop @Watch('onSettingMessageFn') settingMessageNum: number; // Incoming click settings
private title: Array<string> = ['', '自拍镜像', '视频防抖', '曝光模式', '对焦模式', '拍摄质量', '拍摄时显示地理位置', '照片格式', '照片方向配置', '照片分辨率', '视频分辨率', '录像帧率', '参考线',];
private settingItemDataList: Array<Array<string>> = [
[], [],
['关闭视频防抖', '基础防抖算法', '一般防抖算法', '最好防抖算法', '自动进行选择'],
['锁定曝光模式', '自动曝光模式', '连续自动曝光'],
['手动对焦', '连续自动对焦', '自动变焦', '对焦锁定'],
['高', '中', '差'],
[],
['PNG', 'JPG', 'BMP', 'WEBP', 'JPEG'],
['0', '90', '180', '270'],
['1920*1080', '1280*720', '640*480'],
['1920*1080', '1280*720', '640*480'],
['15', '30'],
];
@State isIndex: number = 0;
private settingItemNumberArray = [2,3,4,5,7,8,9,10,11];
onSettingMessageFn() {
switch (this.settingMessageNum) {
case 2:
this.isIndex = globalThis.settingDataObj.videoStabilizationMode;
break;
case 3:
this.isIndex = globalThis.settingDataObj.exposureMode;
break;
case 4:
this.isIndex = globalThis.settingDataObj.focusMode;
break;
case 5:
this.isIndex = globalThis.settingDataObj.photoQuality;
break;
case 7:
this.isIndex = globalThis.settingDataObj.photoFormat;
break;
case 8:
this.isIndex = globalThis.settingDataObj.photoOrientation;
break;
case 9:
this.isIndex = globalThis.settingDataObj.photoResolution;
break;
case 10:
this.isIndex = globalThis.settingDataObj.videoResolution;
break;
case 11:
this.isIndex = globalThis.settingDataObj.videoFrame;
break;
}
}
getModeIconObj() {
if (this.settingMessageNum == 1) {
return { icon: $r('app.media.pic_camera_mirror'), message: '自拍镜像功能只能在前置摄像头打开时可使用。' };
} else if (this.settingMessageNum == 6) {
return { icon: $r('app.media.pic_camera_mirror'), message: '显示地理位置,用于记录照片或视频拍摄地理位置信息。' };
} else if (this.settingMessageNum == 12) {
return { icon: $r('app.media.pic_camera_line'), message: '打开相机参考线,可以帮你创造出构图更出色的画面。' };
} else {
return { icon: null, message: null };
}
}
build() {
Column() {
Row() {
Text(this.title[this.settingMessageNum])
.fontSize(24)
.fontWeight(700)
.fontColor('#182431')
.width('96%')
.textAlign(TextAlign.Start)
}.margin({ top: 20 })
if (this.settingMessageNum == 1 || this.settingMessageNum == 6 || this.settingMessageNum == 12) {
Column() {
Image(this.getModeIconObj().icon).width(450).height(350).objectFit(ImageFit.ScaleDown);
Text(this.getModeIconObj().message).fontColor('#182431').fontSize(18).fontWeight(400);
}.margin({ top: 90 })
} else {
Column() {
ForEach(this.settingItemDataList[this.settingMessageNum], (item, index) => {
settingItem({
itemData: item,
index: index,
isIndex: $isIndex,
settingMessageNum: this.settingMessageNum
})
})
}
.margin({ top: 20 })
.borderRadius(24)
.width(720)
.backgroundColor(Color.White)
}
}
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import UIAbility from '@ohos.app.ability.UIAbility';
import prompt from '@system.prompt'
import window from '@ohos.window';
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
import { Permissions } from '@ohos.abilityAccessCtrl';
import hilog from '@ohos.hilog';
const TAG: string = "EntryAbility";
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) {
hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? '');
hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:' + JSON.stringify(launchParam) ?? '');
// @ts-ignore
globalThis.abilityContext = this.context;
}
onDestroy() {
hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage) {
// Main window is created, set main page for this ability
console.log("[Demo] MainAbility onWindowStageCreate")
windowStage.loadContent("pages/Index", (err, data) => {
if (err.code) {
console.error('Failed to load the content. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in loading the content. Data: ' + JSON.stringify(data))
});
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file Date tool
*/
export default class DateTimeUtil {
/**
* Hour, minute, second
*/
getTime() {
const DATETIME = new Date();
return this.concatTime(DATETIME.getHours(), DATETIME.getMinutes(), DATETIME.getSeconds());
}
/**
* Year, Month, Day
*/
getDate() {
const DATETIME = new Date();
return this.concatDate(DATETIME.getFullYear(), DATETIME.getMonth() + 1, DATETIME.getDate());
}
/**
* Add 0 if the date is less than two digits
* @param value-data value
*/
fill(value: number) {
return (value > 9 ? '' : '0') + value;
}
/**
* Recording time timer
* @param millisecond-data value
*/
getVideoTime(millisecond: number): string {
let minute = Math.floor(millisecond / 60000);
let second = Math.floor((millisecond - minute * 60000) / 1000);
return `${this.fill(minute)} : ${this.fill(second)}`;
}
/**
* Format modification of year, month, day
* @param year
* @param month
* @param date
*/
concatDate(year: number, month: number, date: number) {
return `${year}${this.fill(month)}${this.fill(date)}`;
}
/**
* Time minute second format modification
* @param hours
* @param minutes
* @param seconds
*/
concatTime(hours: number, minutes: number, seconds: number) {
return `${this.fill(hours)}${this.fill(minutes)}${this.fill(seconds)}`;
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import hilog from '@ohos.hilog';
class Logger {
private domain: number;
private prefix: string;
private format: string = "%{public}s, %{public}s";
constructor(prefix: string) {
this.prefix = prefix;
this.domain = 0xFF00;
}
debug(...args: any[]) {
hilog.debug(this.domain, this.prefix, this.format, args);
}
info(...args: any[]) {
hilog.info(this.domain, this.prefix, this.format, args);
}
warn(...args: any[]) {
hilog.warn(this.domain, this.prefix, this.format, args);
}
error(...args: any[]) {
hilog.error(this.domain, this.prefix, this.format, args);
}
}
export default new Logger('[Sample_Camera]');

View File

@ -0,0 +1,207 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @ts-nocheck
import mediaLibrary from '@ohos.multimedia.mediaLibrary';
import DateTimeUtil from '../model/DateTimeUtil';
import Logger from '../model/Logger';
export default class MediaUtils {
private tag: string = 'MediaUtils';
private mediaTest: mediaLibrary.MediaLibrary = mediaLibrary.getMediaLibrary(globalThis.abilityContext);
private static instance: MediaUtils = new MediaUtils();
private num: number = 0;
public static getInstance() {
if (this.instance === undefined) {
this.instance = new MediaUtils();
}
return this.instance;
}
async createAndGetUri(mediaType: number) {
let info = this.getInfoFromType(mediaType);
let dateTimeUtil = new DateTimeUtil();
let name = `${dateTimeUtil.getDate()}_${dateTimeUtil.getTime()}`;
let displayName = `${info.prefix}${name}${info.suffix}`;
Logger.info(this.tag, `createAndGetUri displayName = ${displayName},mediaType = ${mediaType}`);
let publicPath = await this.mediaTest.getPublicDirectory(info.directory);
Logger.info(this.tag, `createAndGetUri publicPath = ${publicPath}`);
try {
return await this.mediaTest.createAsset(mediaType, displayName, publicPath);
} catch {
this.num++;
displayName = `${info.prefix}${name}_${this.num}${info.suffix}`;
return await this.mediaTest.createAsset(mediaType, displayName, publicPath);
}
}
async queryFile(dataUri: any) {
let fileKeyObj = mediaLibrary.FileKey;
if (dataUri !== undefined) {
let args = dataUri.id.toString();
let fetchOp = {
selections: `${fileKeyObj.ID}=?`,
selectionArgs: [args],
}
const fetchFileResult = await this.mediaTest.getFileAssets(fetchOp);
Logger.info(this.tag, `fetchFileResult.getCount() = ${fetchFileResult.getCount()}`);
const fileAsset = await fetchFileResult.getAllObject();
return fileAsset[0];
}
}
async getFdPath(fileAsset: any) {
let fd = await fileAsset.open('Rw');
Logger.info(this.tag, `fd = ${fd}`);
return fd;
}
async createFile(mediaType: number) {
let dataUri = await this.createAndGetUri(mediaType);
if (dataUri) {
let fileAsset = await this.queryFile(dataUri);
if (fileAsset) {
let fd = await this.getFdPath(fileAsset);
return fd;
}
}
}
async getFileAssetsFromType(mediaType: number) {
Logger.info(this.tag, `getFileAssetsFromType,mediaType = ${mediaType}`);
let fileKeyObj = mediaLibrary.FileKey;
let fetchOp = {
selections: `${fileKeyObj.MEDIA_TYPE}=?`,
selectionArgs: [`${mediaType}`],
}
const fetchFileResult = await this.mediaTest.getFileAssets(fetchOp);
Logger.info(this.tag, `getFileAssetsFromType,fetchFileResult.count = ${fetchFileResult.getCount()}`);
let fileAssets = [];
if (fetchFileResult.getCount() > 0) {
fileAssets = await fetchFileResult.getAllObject();
}
return fileAssets;
}
async getAlbums() {
Logger.info(this.tag, 'getAlbums begin');
let albums = [];
const [ files, images, videos, audios ] = await Promise.all([
this.getFileAssetsFromType(mediaLibrary.MediaType.FILE),
this.getFileAssetsFromType(mediaLibrary.MediaType.IMAGE),
this.getFileAssetsFromType(mediaLibrary.MediaType.VIDEO),
this.getFileAssetsFromType(mediaLibrary.MediaType.AUDIO)
]);
albums.push({
albumName: 'Documents', count: files.length, mediaType: mediaLibrary.MediaType.FILE
});
albums.push({
albumName: 'Pictures', count: images.length, mediaType: mediaLibrary.MediaType.IMAGE
});
albums.push({
albumName: 'Camera', count: videos.length, mediaType: mediaLibrary.MediaType.VIDEO
});
albums.push({
albumName: 'Audios', count: audios.length, mediaType: mediaLibrary.MediaType.AUDIO
});
return albums;
}
deleteFile(media: any) {
let uri = media.uri;
Logger.info(this.tag, `deleteFile,uri = ${uri}`);
return this.mediaTest.deleteAsset(uri);
}
onDateChange(callback: () => void) {
this.mediaTest.on('albumChange', () => {
Logger.info(this.tag, 'albumChange called');
callback();
})
this.mediaTest.on('imageChange', () => {
Logger.info(this.tag, 'imageChange called');
callback();
})
this.mediaTest.on('audioChange', () => {
Logger.info(this.tag, 'audioChange called');
callback();
})
this.mediaTest.on('videoChange', () => {
Logger.info(this.tag, 'videoChange called');
callback();
})
this.mediaTest.on('fileChange', () => {
Logger.info(this.tag, 'fileChange called');
callback();
})
}
offDateChange() {
this.mediaTest.off('albumChange');
this.mediaTest.off('imageChange');
this.mediaTest.off('audioChange');
this.mediaTest.off('videoChange');
this.mediaTest.off('fileChange');
}
// Photo Format
onChangePhotoFormat() {
if (globalThis.settingDataObj.photoFormat == 0) {
return 'png';
}
if (globalThis.settingDataObj.photoFormat == 1) {
return 'jpg';
}
if (globalThis.settingDataObj.photoFormat == 2) {
return 'bmp';
}
if (globalThis.settingDataObj.photoFormat == 3) {
return 'webp';
}
if (globalThis.settingDataObj.photoFormat == 4) {
return 'jpeg';
}
}
getInfoFromType(mediaType: number) {
let result = {
prefix: '', suffix: '', directory: 0
};
switch (mediaType) {
case mediaLibrary.MediaType.FILE:
result.prefix = 'FILE_';
result.suffix = '.txt';
result.directory = mediaLibrary.DirectoryType.DIR_DOCUMENTS;
break;
case mediaLibrary.MediaType.IMAGE:
result.prefix = 'IMG_';
result.suffix = `.${this.onChangePhotoFormat()}`;
result.directory = mediaLibrary.DirectoryType.DIR_CAMERA;
break;
case mediaLibrary.MediaType.VIDEO:
result.prefix = 'VID_';
result.suffix = '.mp4';
result.directory = mediaLibrary.DirectoryType.DIR_CAMERA;
break;
case mediaLibrary.MediaType.AUDIO:
result.prefix = 'AUD_';
result.suffix = '.wav';
result.directory = mediaLibrary.DirectoryType.DIR_AUDIO;
break;
}
return result;
}
}

View File

@ -0,0 +1,228 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
import cameraDemo from 'libentry.so';
import Logger from '../model/Logger';
import { mainDialog } from '../Dialog/MainDialog';
import { dividerPage } from '../views/DividerPage';
import { settingDialog } from '../Dialog/SettingDialog';
import { CountdownPage } from '../views/CountdownPage';
import { FlashingLightPage } from '../views/FlashingLightPage';
import { SlidePage } from '../views/SlidePage';
import { modeSwitchPage } from '../views/ModeSwitchPage';
import { focusPage } from '../views/FocusPage';
import { FocusAreaPage } from '../views/FocusAreaPage';
import { Constants } from '../common/Constants';
import DisplayCalculator from '../common/DisplayCalculator';
import display from '@ohos.display';
const TAG: string = 'UI indexPage';
globalThis.settingDataObj = {
mirrorBol: false, // Mirror Enable -> Off
videoStabilizationMode: 0, // Video Anti Shake -> Off
exposureMode: 1, // Exposure mode -> Automatic
focusMode: 2, // Focus mode -> Automatic
photoQuality: 1, // Photo quality -> medium
locationBol: false, // Show Geographic Location -> Off
photoFormat: 1, // Photo Format -> JPG
photoOrientation: 0, // Photo direction -> 0
photoResolution: 0, // Photo resolution -> 1920 * 1080
videoResolution: 0, // Photo resolution -> 1920 * 1080
videoFrame: 0, // Recording frame rate -> 15
referenceLineBol: false // Divider -> Off
};
@Entry
@Component
struct Index {
// XComponentController
private mXComponentController: XComponentController = new XComponentController();
// surfaceID value
@State surfaceId: string = '';
// Entrance confirmation network pop-up
private mainDialogController: CustomDialogController = new CustomDialogController({
builder: mainDialog(),
autoCancel: false,
customStyle: true
});
// Select mode
@State modelBagCol: string = 'photo';
// Exposure area
@State focusPointBol: boolean = false;
// Finger click coordinates in the exposure area
@State focusPointVal: Array<number> = [0, 0];
// Display where scale, focal length value, and focus box cannot coexist
@State exposureBol: boolean = true;
// Exposure value
@State exposureNum: number = 0;
// Countdown, photography, and video recording
@State countdownNum: number = 0;
// Front and rear cameras
@State cameraDeviceIndex: number = 0;
@State xComponentWidth: number = 384;
@State xComponentHeight: number = 450;
private deviceType: string;
private screenHeight: number;
private screenWidth: number;
// Set Popup Box
private settingDialogController: CustomDialogController = new CustomDialogController({
builder: settingDialog({
referenceLineBol: $referenceLineBol,
surfaceId: this.surfaceId,
cameraDeviceIndex: this.cameraDeviceIndex
}),
autoCancel: false,
customStyle: true
});
// REFERENCE LINE
@State referenceLineBol: boolean = false;
@StorageLink('defaultAspectRatio') @Watch('initXComponentSize') defaultAspectRatio: number = Constants.MIN_ASPECT_RATIO;
@State onShow: boolean = false;
atManager = abilityAccessCtrl.createAtManager();
// Entry initialization function
async aboutToAppear() {
await this.requestPermissionsFn();
let mDisplay = display.getDefaultDisplaySync();
this.screenWidth = px2vp(mDisplay.width);
this.screenHeight = px2vp(mDisplay.height);
// @ts-ignore
this.deviceType = AppStorage.get<string>('deviceType');
if (this.deviceType === Constants.TABLET) {
this.defaultAspectRatio = Constants.MAX_ASPECT_RATIO;
}
this.initXComponentSize();
}
initXComponentSize(): void {
let defaultSize = DisplayCalculator.calcSurfaceDisplaySize(this.screenWidth, this.screenHeight, this.defaultAspectRatio);
this.xComponentWidth = defaultSize.width;
this.xComponentHeight = defaultSize.height;
}
async aboutToDisAppear() {
await cameraDemo.releaseCamera();
}
// Obtain permissions
async requestPermissionsFn() {
Logger.info(TAG, `requestPermissionsFn entry`);
try {
this.atManager.requestPermissionsFromUser(globalThis.abilityContext, [
'ohos.permission.CAMERA',
'ohos.permission.MICROPHONE',
'ohos.permission.READ_MEDIA',
'ohos.permission.WRITE_MEDIA'
]).then(() => {
Logger.info(TAG, `request Permissions success!`);
this.onShow = true;
})
.catch((error) => {
Logger.info(TAG, `requestPermissionsFromUser call Failed! error: ${error.code}`);
})
} catch (err) {
Logger.info(TAG, `requestPermissionsFromUser call Failed! error: ${err.code}`);
}
}
async onPageShow() {
Logger.info(TAG, `onPageShow App`);
if (this.surfaceId && this.onShow) {
Logger.error(TAG, `initCamera start`);
cameraDemo.initCamera(this.surfaceId, globalThis.settingDataObj.focusMode, this.cameraDeviceIndex);
Logger.error(TAG, `initCamera end`);
}
}
onPageHide() {
Logger.info(TAG, `onPageHide App`);
cameraDemo.releaseCamera();
}
build() {
Stack() {
if (this.onShow) {
// general appearance of a picture
XComponent({
id: 'componentId',
type: 'surface',
controller: this.mXComponentController
})
.onLoad(async () => {
Logger.info(TAG, 'onLoad is called');
this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
Logger.info(TAG, `onLoad surfaceId: ${this.surfaceId}`);
Logger.error(TAG, `initCamera start`);
cameraDemo.initCamera(this.surfaceId, globalThis.settingDataObj.focusMode, this.cameraDeviceIndex);
globalThis.cameraDeviceIndex = this.cameraDeviceIndex;
Logger.error(TAG, `initCamera end`);
})
.backgroundColor(Color.Blue)
.width('100%')
.height('100%')
// REFERENCE LINE
dividerPage({ referenceLineBol: this.referenceLineBol });
// Set icon
Button() {
Image($r('app.media.icon_camera_setting'))
.width('120px').height('120px')
}
.width('120px')
.height('120px')
.backgroundColor('rgba(255,255,255,0.20)')
.borderRadius('40px')
.position({ x: '80%', y: '3%' })
.onClick(() => {
// Open the settings pop-up box
this.settingDialogController.open()
});
// Exposure frame and focus frame
focusPage({
focusPointBol: $focusPointBol,
focusPointVal: $focusPointVal,
exposureBol: $exposureBol,
exposureNum: $exposureNum
});
// Exposure focusing finger click area
FocusAreaPage({
focusPointBol: $focusPointBol,
focusPointVal: $focusPointVal,
exposureBol: $exposureBol,
exposureNum: $exposureNum,
xComponentWidth: this.xComponentWidth,
xComponentHeight: this.xComponentHeight
});
// CountDown
CountdownPage({ countdownNum: $countdownNum });
// FlashLight
FlashingLightPage();
// Slide
SlidePage();
// Reverse camera_Multiple workstations_Take photos_Video
modeSwitchPage({
surfaceId: this.surfaceId,
cameraDeviceIndex: $cameraDeviceIndex,
countdownNum: $countdownNum
});
}
}
.height('100%')
.width('100%')
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Countdown page
@Component
export struct CountdownPage {
@Link countdownNum: number; // Countdown value
private countdownListData: Array<string> = ['2', '5', '10']; // Loop rendering
@State countdownBol: boolean = true;
build() {
Row() {
if (this.countdownBol && !this.countdownNum) {
Row() {
Button() {
Image($r('app.media.icon_camera_setting_timer'))
.width('60px').height('60px');
}
.width('80px')
.height('80px')
.backgroundColor('rgba(255,255,255,0.20)')
.borderRadius('40px')
.onClick(() => {
this.countdownBol = false;
})
}
}
if (this.countdownNum && this.countdownBol) {
Row() {
Image($r('app.media.icon_camera_setting_timer_on')).width('60px').height('60px').margin({ left: 5 });
Text(this.countdownNum + '').fontSize(21).fontWeight(500).margin({ left: 5 }).fontColor(Color.White);
}
.backgroundColor('rgba(255,255,255,0.20)')
.borderRadius('40px')
.width('140px')
.height('80px')
.onClick(() => {
this.countdownBol = false;
})
}
if (!this.countdownBol) {
Row() {
Image($r('app.media.icon_camera_setting_timer_on_balk')).width('60px').height('60px').margin({ left: 5 });
ForEach(this.countdownListData, (item) => {
Text(item).fontSize(21).fontWeight(500).margin({
left: 10
})
.fontColor(this.countdownNum == item ? $r('app.color.theme_color') : '#182431').onClick(() => {
this.countdownNum = item
this.countdownBol = true
})
}, item => item)
Text('OFF').fontSize(21).fontWeight(500).margin({
left: 10
})
.fontColor(this.countdownNum == 0 ? $r('app.color.theme_color') : '#182431').onClick(() => {
this.countdownNum = 0;
this.countdownBol = true;
})
}
.backgroundColor('#FFFFFF')
.borderRadius('40px')
.width('360px')
.height('80px')
.zIndex(999)
}
}
.position({ x: 30, y: 352 })
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@Component
export struct dividerPage {
@Prop referenceLineBol: boolean
build() {
if (this.referenceLineBol) {
Stack() {
Column() {
Divider().color(Color.White);
Divider().color(Color.White);
}.justifyContent(FlexAlign.SpaceEvenly).height('100%');
Row() {
Divider().vertical(true).color(Color.White);
Divider().vertical(true).color(Color.White);
}.justifyContent(FlexAlign.SpaceEvenly).width('100%');
}
}
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Flash page
import cameraDemo from 'libentry.so';
@Component
export struct FlashingLightPage {
// Page judgment
@State flashingBol: boolean = true;
// Flash mode
@State flashingNum: number = 0;
// Return to selected image
getImageDefault() {
if (this.flashingNum == 0) {
return $r('app.media.ic_camera_public_flash_off');
}
if (this.flashingNum == 1) {
return $r('app.media.ic_camera_public_flash_on');
}
if (this.flashingNum == 2) {
return $r('app.media.ic_camera_public_flash_auto');
}
if (this.flashingNum == 3) {
return $r('app.media.flash_always_on');
}
}
build() {
Row() {
if (this.flashingBol) {
Row() {
Button() {
Image(this.getImageDefault())
.width('60px').height('60px').fillColor('#FFFFFF');
}
.width('80px')
.height('80px')
.backgroundColor('rgba(255,255,255,0.20)')
.borderRadius('40px')
.onClick(() => {
this.flashingBol = false;
})
}
} else {
Flex({ justifyContent: FlexAlign.SpaceEvenly, alignItems: ItemAlign.Center }) {
Image($r('app.media.ic_camera_public_flash_auto'))
.width('60px')
.height('60px')
.fillColor(this.flashingNum == 2 ? $r('app.color.theme_color') : '')
.onClick(() => {
this.flashingNum = 2;
this.flashingBol = true;
cameraDemo.hasFlash(this.flashingNum);
});
Image($r('app.media.ic_camera_public_flash_off'))
.width('60px')
.height('60px')
.fillColor(this.flashingNum == 0 ? $r('app.color.theme_color') : '')
.onClick(() => {
this.flashingNum = 0;
this.flashingBol = true;
cameraDemo.hasFlash(this.flashingNum);
});
Image($r('app.media.ic_camera_public_flash_on'))
.width('60px')
.height('60px')
.fillColor(this.flashingNum == 1 ? $r('app.color.theme_color') : '')
.onClick(() => {
this.flashingNum = 1;
this.flashingBol = true;
cameraDemo.hasFlash(this.flashingNum);
});
Image($r('app.media.flash_always_on'))
.width('50px')
.height('50px')
.fillColor(this.flashingNum == 3 ? $r('app.color.theme_color') : '')
.onClick(() => {
this.flashingNum = 3;
this.flashingBol = true;
cameraDemo.hasFlash(this.flashingNum);
});
}
.backgroundColor('#FFFFFF')
.borderRadius('40px')
.width('300px')
.height('80px')
.zIndex(999)
}
}
.position({ x: 30, y: 408 })
}
}

View File

@ -0,0 +1,100 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import cameraDemo from 'libentry.so';
import Logger from '../model/Logger';
const TAG: string = 'FocusAreaPage';
// Focus Area
@Component
export struct FocusAreaPage {
@Link focusPointBol: boolean;
@Link focusPointVal: Array<number>;
// Display where scale, focal length value, and focus box cannot coexist
@Link exposureBol: boolean;
// Exposure value
@Link exposureNum: number;
@Prop xComponentWidth: number;
@Prop xComponentHeight: number;
// Focusing area display box timer
private areaTimer: number = -1;
// Sliding Exposure Up and Down
private panOption: PanGestureOptions = new PanGestureOptions({
direction: PanDirection.Up | PanDirection.Down,
fingers: 1
});
build() {
Row() {}
.width(this.xComponentWidth)
.height(this.xComponentHeight)
.opacity(1)
.onTouch((e: TouchEvent) => {
if (e.type === TouchType.Down) {
this.focusPointBol = true;
this.focusPointVal[0] = e.touches[0].screenX;
this.focusPointVal[1] = e.touches[0].screenY;
// Focus point
// @ts-ignore
cameraDemo.isFocusPoint(
e.touches[0].screenX / this.xComponentWidth,
e.touches[0].screenY / this.xComponentHeight
);
// @ts-ignore
cameraDemo.isMeteringPoint(
e.touches[0].screenX / this.xComponentWidth,
e.touches[0].screenY / this.xComponentHeight + 50
);
}
if (e.type === TouchType.Up) {
if (this.areaTimer) {
clearTimeout(this.areaTimer);
}
this.areaTimer = setTimeout(() => {
this.focusPointBol = false;
}, 3500);
}
})
// Trigger this gesture event by dragging vertically with one finger
.gesture(
PanGesture(this.panOption)
.onActionStart(() => {
Logger.info(TAG, 'PanGesture onActionStart');
this.exposureBol = false;
})
.onActionUpdate((event: GestureEvent) => {
let offset = -event.offsetY;
if (offset > 200) {
this.exposureNum = 4;
}
if (offset < -200) {
this.exposureNum = -4;
}
if (offset > -200 && offset < 200) {
this.exposureNum = Number((offset / 50).toFixed(1));
}
// Exposure Compensation -4 +4
cameraDemo.isExposureBiasRange(this.exposureNum);
Logger.info(TAG, `PanGesture onActionUpdate offset: ${offset}, exposureNum: ${this.exposureNum}`);
})
.onActionEnd(() => {
this.exposureNum = 0;
this.exposureBol = true;
Logger.info(TAG, 'PanGesture onActionEnd end');
})
)
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 曝光选择
@Component
export struct focusPage {
@Link focusPointBol: boolean;
@Link focusPointVal: Array<number>;
// Display where scale, focal length value, and focus box cannot coexist
@Link exposureBol: boolean;
// Exposure value
@Link exposureNum: number;
build() {
if (this.focusPointBol) {
Row() {
if (this.exposureBol) {
// Focus frame
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween }) {
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
Row() {
}.border({
width: { left: 1.6, right: 0, top: 1.6, bottom: 0 },
color: Color.White,
radius: { topLeft: 10, topRight: 0, bottomLeft: 0, bottomRight: 0 }
}).size({ width: 15, height: 15 });
Row() {
}.border({
width: { left: 0, right: 1.6, top: 1.6, bottom: 0 },
color: Color.White,
radius: { topLeft: 0, topRight: 10, bottomLeft: 0, bottomRight: 0 }
}).size({ width: 15, height: 15 });
}
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
Row() {
}.border({
width: { left: 1.6, right: 0, top: 0, bottom: 1.6 },
color: Color.White,
radius: { topLeft: 0, topRight: 0, bottomLeft: 10, bottomRight: 0 }
}).size({ width: 15, height: 15 });
Row() {
}.border({
width: { left: 0, right: 1.6, top: 0, bottom: 1.6 },
color: Color.White,
radius: { topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 10 }
}).size({ width: 15, height: 15 });
}
}
.width(50)
.height(50)
.position({ x: this.focusPointVal[0] - 60, y: this.focusPointVal[1] - 60 });
} else {
// Focus value
Text(this.exposureNum + '').fontSize(60).fontColor(Color.White).fontWeight(400).position({
x: this.focusPointVal[0] - 58,
y: this.focusPointVal[1] - 30
});
// Scale value
Flex() {
Column() {
Text('+4').fontColor(Color.White);
Text('0').margin({ top: 50, bottom: 50 }).fontColor(Color.White);
Text('-4').fontColor(Color.White);
}.margin({ right: 9 });
// Scale
Column() {
Text('').height(67).border({
width: { left: '0', right: 4, top: '0', bottom: '0' },
color: Color.White,
radius: 2,
style: BorderStyle.Dotted
});
Text('').height(8).border({
width: { left: '0', right: 8, top: '0', bottom: '0' },
color: Color.White,
radius: 4,
style: BorderStyle.Solid
}).margin({ top: 4 })
Text('').height(67).border({
width: { left: '0', right: 4, top: '0', bottom: '0' },
color: Color.White,
radius: 2,
style: BorderStyle.Dotted
}).margin({ top: 4 });
}
}.position({
x: this.focusPointVal[0] + 56.6,
y: this.focusPointVal[1] - 73
});
}
// Exposure icon
Image($r('app.media.ic_public_brightness')).size({ width: 24, height: 24 })
.position({
x: this.focusPointVal[0] + 10,
y: this.focusPointVal[1] - 30
});
}.zIndex(99)
}
}
}

View File

@ -0,0 +1,453 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Reverse camera_ Multiple workstations_ Take photos_ Video
import DateTimeUtil from '../model/DateTimeUtil';
import Logger from '../model/Logger';
import cameraDemo from 'libentry.so';
import mediaLibrary from '@ohos.multimedia.mediaLibrary';
import image from '@ohos.multimedia.image';
import media from '@ohos.multimedia.media';
import MediaUtils from '../model/MediaUtils';
import deviceInfo from '@ohos.deviceInfo';
import AVRecorder from '@ohos.multimedia.media';
import fileio from '@ohos.fileio';
const CameraSize = {
WIDTH: 1280,
HEIGHT: 720
};
globalThis.photoSettings = {
quality: 0, // Photo quality
rotation: 0, // Photo direction
mirror: false, // Mirror Enable
latitude: 12.9698, // geographic location
longitude: 77.7500, // geographic location
altitude: 1000 // geographic location
};
@Component
export struct modeSwitchPage {
private tag: string = 'CAIHF modeSwitchPage:';
private mediaUtil = MediaUtils.getInstance();
private fileAsset: mediaLibrary.FileAsset = undefined;
private fd: number = -1;
@State videoId: string = '';
@State mSurfaceId: string = '';
private mReceiver: image.ImageReceiver = undefined;
private videoRecorder: media.AVRecorder = undefined;
private videoConfig: media.AVRecorderConfig = {
audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
profile: {
audioBitrate: 48000,
audioChannels: 2,
audioCodec: media.CodecMimeType.AUDIO_AAC,
audioSampleRate: 48000,
fileFormat: media.ContainerFormatType.CFT_MPEG_4,
videoBitrate: 512000,
videoCodec: media.CodecMimeType.VIDEO_AVC,
videoFrameWidth: 640,
videoFrameHeight: 480,
videoFrameRate: 30
},
url: '',
rotation: 0
};
private photoRotationMap = {
rotation0: 0,
rotation90: 90,
rotation180: 180,
rotation270: 270,
};
// Front and rear cameras
@Link cameraDeviceIndex: number;
// SurfaceID
@Prop surfaceId: string;
// Countdown value
@Link countdownNum: number;
// Countdown timer
@State countTimerInt: number = -1;
@State countTimerOut: number = -1;
// Photo Thumbnails
@State imgThumbnail: string = undefined;
// Recording time
@State videoRecodeTime: number = 0;
// Recording time timer
@State timer: number = undefined;
// Time Manager
@State dateTimeUtil: DateTimeUtil = new DateTimeUtil();
// Select mode
@State modelBagCol: string = 'photo';
// Choose camera or capture
@State @Watch('onChangeIsModeBol') isModeBol: boolean = true;
// Video Thumbnails
@State videoThumbnail: image.PixelMap = undefined;
// After pausing, click 'stop' to reset the pause to default
onChangeIsModeBol() {
}
// Countdown capture and video
countTakeVideoFn() {
if (this.countdownNum) {
// Clear Countdown
if (this.countTimerOut) {
clearTimeout(this.countTimerOut);
}
if (this.countTimerInt) {
clearInterval(this.countTimerInt);
}
// Turn on timer
this.countTimerOut = setTimeout(() => {
// Determine whether it is in video or photo mode
this.isVideoPhotoFn();
}, this.countdownNum * 1000)
// Turn on timer
this.countTimerInt = setInterval(() => {
this.countdownNum--;
if (this.countdownNum === 0) {
clearInterval(this.countTimerInt);
}
}, 1000)
} else {
this.isVideoPhotoFn();
}
}
async getVideoSurfaceID(){
Logger.info(this.tag, `getVideoSurfaceID`);
this.videoRecorder = await media.createAVRecorder();
Logger.info(this.tag, `getVideoSurfaceID videoRecorder: ${this.videoRecorder}`);
this.fileAsset = await this.mediaUtil.createAndGetUri(mediaLibrary.MediaType.VIDEO);
Logger.info(this.tag, `getVideoSurfaceID fileAsset: ${this.fileAsset}`);
this.fd = await this.mediaUtil.getFdPath(this.fileAsset);
Logger.info(this.tag, `getVideoSurfaceID fd: ${this.fd}`);
this.videoConfig.url = `fd://${this.fd}`;
Logger.info(this.tag, `getVideoSurfaceID videoConfig.url : ${this.videoConfig.url }`);
if (deviceInfo.deviceType == 'default') {
Logger.info(this.tag, `deviceType = default`);
this.videoConfig.videoSourceType = media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_ES;
}
if (deviceInfo.deviceType == 'phone') {
Logger.info(this.tag, `deviceType = phone`)
this.videoConfig.videoSourceType = media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV;
this.videoConfig.profile.videoCodec = media.CodecMimeType.VIDEO_MPEG4;
if (this.cameraDeviceIndex == 1) {
this.videoConfig.rotation = this.photoRotationMap.rotation270;
} else {
this.videoConfig.rotation = this.photoRotationMap.rotation90;
}
}
if (deviceInfo.deviceType == 'tablet') {
Logger.info(this.tag, `deviceType = tablet`);
this.videoConfig.videoSourceType = media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV;
}
this.videoConfig.profile.videoFrameWidth = cameraDemo.getVideoFrameWidth();
this.videoConfig.profile.videoFrameHeight = cameraDemo.getVideoFrameHeight();
this.videoConfig.profile.videoFrameRate = cameraDemo.getVideoFrameRate();
await this.videoRecorder.prepare(this.videoConfig);
this.videoId = await this.videoRecorder.getInputSurface();
Logger.info(this.tag, `getVideoSurfaceID videoId: ${this.videoId}`);
}
createImageReceiver() {
try {
this.mReceiver = image.createImageReceiver(CameraSize.WIDTH, CameraSize.HEIGHT, 2000, 8)
Logger.info(this.tag, `createImageReceiver value: ${this.mReceiver} `);
this.mReceiver.on('imageArrival', () => {
Logger.info(this.tag, 'imageArrival start');
this.mReceiver.readNextImage((err, image) => {
Logger.info(this.tag, 'readNextImage start');
if (err || image === undefined) {
Logger.error(this.tag, 'readNextImage failed ');
return;
}
image.getComponent(4, (errMsg, img) => {
Logger.info(this.tag, 'getComponent start');
if (errMsg || img === undefined) {
Logger.info(this.tag, 'getComponent failed ');
return;
}
let buffer
if (img.byteBuffer) {
buffer = img.byteBuffer;
} else {
Logger.error(this.tag, 'img.byteBuffer is undefined');
}
this.savePicture(buffer, image);
})
})
})
} catch {
Logger.info(this.tag, 'savePicture err');
}
}
// Read Image
async savePicture(buffer: ArrayBuffer, img: image.Image) {
try {
Logger.info(this.tag, 'savePicture start');
let imgFileAsset = await this.mediaUtil.createAndGetUri(mediaLibrary.MediaType.IMAGE);
let imgPhotoUri = imgFileAsset.uri;
Logger.info(this.tag, `photoUri = ${imgPhotoUri}`);
let imgFd = await this.mediaUtil.getFdPath(imgFileAsset);
Logger.info(this.tag, `fd = ${imgFd}`);
await fileio.write(imgFd, buffer);
await imgFileAsset.close(imgFd);
await img.release();
Logger.info(this.tag, 'save image End');
if (this.handleTakePicture) {
this.handleTakePicture(imgPhotoUri);
}
} catch (err) {
Logger.info(this.tag, 'savePicture err' + JSON.stringify(err.message));
}
}
async getPhotoSurfaceID() {
if(this.mReceiver) {
Logger.info(this.tag, 'imageReceiver has been created');
} else {
this.createImageReceiver();
}
this.mSurfaceId = await this.mReceiver.getReceivingSurfaceId();
if(this.mSurfaceId) {
Logger.info(this.tag, `createImageReceiver mSurfaceId: ${this.mSurfaceId} `);
} else {
Logger.info(this.tag, `Get mSurfaceId failed `);
}
}
// Determine the video or photo mode
async isVideoPhotoFn() {
await this.getPhotoSurfaceID();
if (this.modelBagCol == 'photo') {
cameraDemo.startPhotoOrVideo(this.modelBagCol, this.videoId, this.mSurfaceId);
} else if (this.modelBagCol == 'video') {
this.isModeBol = false;
if (this.timer) {
clearInterval(this.timer);
}
// Start record
await this.getVideoSurfaceID();
cameraDemo.startPhotoOrVideo(this.modelBagCol, this.videoId, this.mSurfaceId);
cameraDemo.videoOutputStart();
this.videoRecorder.start();
}
}
aboutToAppear() {
}
handleTakePicture = (thumbnail: string) => {
this.imgThumbnail = thumbnail;
Logger.info(this.tag, `takePicture end , thumbnail: ${this.imgThumbnail}`);
}
build() {
if (this.isModeBol) {
Column() {
Text('拍照')
.backgroundColor(this.modelBagCol === 'photo' ? $r('app.color.theme_color') : '')
.size({ width: 64, height: 28 })
.borderRadius(14)
.fontSize(14)
.fontColor(Color.White)
.onClick(() => {
this.modelBagCol = 'photo'
})
}.position({ x: '20%', y: '75%' })
Column() {
Text('录像')
.fontSize(14)
.fontColor(Color.White)
.borderRadius(14)
.size({ width: 64, height: 28 })
.backgroundColor(this.modelBagCol === 'video' ? $r('app.color.theme_color') : '')
.onClick(() => {
this.modelBagCol = 'video'
})
}.position({ x: '40%', y: '75%' })
// 图库
Column() {
Row() {
if (this.modelBagCol === 'photo') {
Image(this.imgThumbnail || $r('app.media.pic_avatar_radio02'))
.aspectRatio(1)
.objectFit(ImageFit.Fill)
.border({ width: 2, color: 0xFFFFFF, radius: 40 })
.width('200px')
.height('200px')
} else {
Image(this.videoThumbnail || $r('app.media.pic_avatar_radio02'))
.aspectRatio(1)
.objectFit(ImageFit.Fill)
.border({ width: 2, color: 0xFFFFFF, radius: 40 })
.width('200px')
.height('200px')
}
}.onClick(() => {
if (deviceInfo.deviceType == 'default') {
globalThis.abilityContext.startAbility({
bundleName: 'com.ohos.photos',
abilityName: 'com.ohos.photos.MainAbility'
})
} else if (deviceInfo.deviceType == 'phone') {
globalThis.abilityContext.startAbility({
bundleName: 'com.huawei.hmos.photos',
abilityName: 'com.huawei.hmos.photos.MainAbility'
})
}
})
}.position({ x: '10%', y: '82%' })
// capture video icon
Column() {
Row() {
Button() {
Text()
.width('120px')
.height('120px')
.borderRadius('40px')
.backgroundColor(this.modelBagCol == 'video' ? $r('app.color.theme_color') : Color.White)
}
.border({ width: 3, color: 0xFFFFFF, radius: 70 })
.width('200px')
.height('200px')
.backgroundColor('rgba(255,255,255,0.20)')
.onClick(() => {
// Countdown camera recording - default camera recording
this.countTakeVideoFn();
})
}
}.position({ x: '40%', y: '82%' })
// Front and rear camera switching
Column() {
Row() {
Button() {
Image($r('app.media.switch_camera'))
.width('120px').height('120px')
}
.width('200px')
.height('200px')
.backgroundColor('rgba(255,255,255,0.20)')
.borderRadius('40px')
.onClick(async () => {
// Switching Cameras
this.cameraDeviceIndex ? this.cameraDeviceIndex = 0 : this.cameraDeviceIndex = 1;
// Clear configuration
cameraDemo.releaseSession();
// Start preview
cameraDemo.initCamera(this.surfaceId, globalThis.settingDataObj.focusMode, this.cameraDeviceIndex);
})
}
}.position({ x: '70%', y: '82%' })
} else {
Column() {
Row() {
Text().size({ width: 12, height: 12 }).backgroundColor($r('app.color.theme_color')).borderRadius(6)
Text(this.dateTimeUtil.getVideoTime(this.videoRecodeTime))
.fontSize(30)
.fontColor(Color.White)
.margin({ left: 8 })
}.offset({ x: -580, y: -180 })
}.position({ x: 120, y: 450 })
Column() {
// Video capture button
Button() {
Text().width('120px').height('120px').borderRadius('35px').backgroundColor(Color.White)
}
.border({ width: 2, color: 0xFFFFFF, radius: 45 })
.width('200px')
.height('200px')
.backgroundColor('rgba(255,255,255,0.20)')
.onClick(() => {
cameraDemo.takePictureWithSettings(globalThis.photoSettings);
})
}.position({ x: '10%', y: '82%' })
Column() {
Row() {
Column() {
// 录像停止键
Button() {
Image($r('app.media.ic_camera_video_close')).size({ width: 25, height: 25 });
}
.width('120px')
.height('120px')
.backgroundColor($r('app.color.theme_color'))
.onClick(() => {
if (this.timer) {
clearInterval(this.timer);
}
// Stop video
this.stopVideo().then(async (fileAsset) => {
this.videoRecodeTime = 0;
this.isModeBol = true;
try {
// Get video thumbnail
this.videoThumbnail = await fileAsset.getThumbnail();
} catch (err) {
Logger.info(this.tag, 'videoThumbnail err----------:' + JSON.stringify(err.message));
}
})
})
}
.width('180px')
.height('180px')
.borderRadius('60px')
.backgroundColor($r('app.color.theme_color'))
.justifyContent(FlexAlign.SpaceAround)
}
.justifyContent(FlexAlign.Center)
.border({ width: 3, color: 0xFFFFFF, radius: 70 })
.width('200px')
.height('200px')
.backgroundColor('rgba(255,255,255,0.20)')
}.position({ x: '40%', y: '82%' })
}
}
async stopVideo() {
try {
if (this.videoRecorder) {
await this.videoRecorder.stop();
await this.videoRecorder.release();
}
cameraDemo.videoOutputStopAndRelease();
if (this.fileAsset) {
await this.fileAsset.close(this.fd);
return this.fileAsset;
}
Logger.info(this.tag, 'stopVideo end');
} catch (err) {
Logger.info(this.tag, 'stopVideo err: ' + JSON.stringify(err));
}
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Zoom component
// For changes in components - can only be integers 1 to 6
import cameraDemo from 'libentry.so'
import Logger from '../model/Logger';
const TAG: string = 'SlidePage';
@Component
export struct SlidePage {
// Slide slider
@State outSetValueOne: number = 1;
// Slide slider movement value
@State sliderTextPos: string = '-10';
slideChange(value) {
cameraDemo.setZoomRatio(value);
switch (value) {
case 1:
this.sliderTextPos = '-10';
break;
case 2:
this.sliderTextPos = '70';
break;
case 3:
this.sliderTextPos = '145';
break;
case 4:
this.sliderTextPos = '220';
break;
case 5:
this.sliderTextPos = '300';
break;
case 6:
this.sliderTextPos = '380';
break;
default:
break;
}
}
build() {
Column() {
Row() {
Text('1x').fontColor(Color.White);
Text('6x').fontColor(Color.White);
}.justifyContent(FlexAlign.SpaceBetween).width('95%')
Text(this.outSetValueOne + 'x')
.fontColor('#182431')
.width('100px')
.height('50px')
.borderRadius(25)
.backgroundColor(Color.White)
.fontSize(14)
.textAlign(TextAlign.Center)
.position({ x: this.sliderTextPos, y: '-5' })
.zIndex(9)
Row() {
Slider({
value: this.outSetValueOne,
min: 1,
max: 6,
step: 1,
style: SliderStyle.OutSet
}).showSteps(false)
.trackColor('rgba(255,255,255,0.6)')
.selectedColor($r('app.color.theme_color'))
.onChange((value: number) => {
let val = Number(value.toFixed(2));
this.slideChange(val);
this.outSetValueOne = val;
console.info('value:' + val + 'this.sliderTextPos:' + this.sliderTextPos);
})
}.width('100%')
}
.height('60')
.width('32.5%')
.position({ x: '35%', y: '3%' })
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "MainAbility",
"deviceTypes": [
"default",
"tablet"
],
"metadata": [
{
"name": "ArkTSPartialUpdate",
"value": "true"
}
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntrance": "./ets/entryability/EntryAbility.ts",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"visible": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.CAMERA"
},
{
"name": "ohos.permission.MICROPHONE"
},
{
"name": "ohos.permission.WRITE_MEDIA"
},
{
"name": "ohos.permission.READ_MEDIA"
}
]
}
}

Some files were not shown because too many files have changed in this diff Show More