Merge branch 'master' of gitee.com:openharmony/applications_app_samples into pasteCut
Signed-off-by: zhaojunxia <zhaojunxia@kaihong.com>
23
OAT.xml
@ -117,6 +117,14 @@ Note:If the text contains special characters, please escape them according to th
|
||||
<filteritem type="filepath" name="code\BasicFeature\Media\Audio\screenshots\device\index.png" desc="screenshot"/>
|
||||
<filteritem type="filepath" name="code\BasicFeature\Media\Audio\screenshots\device\VolumePanel.png" desc="screenshot"/>
|
||||
<filteritem type="filepath" name="code\BasicFeature\Media\Audio\screenshots\device\VolumePanel_ChangeVolumLevel.png" desc="screenshot"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Media/AVSession/AvCastPickerForCall/entry/src/main/resources/base/media/icon.png" desc="Provided by the avsession"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Media/AVSession/AvCastPickerForCall/entry/src/main/resources/base/media/startIcon.png" desc="Provided by the avsession"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Media/AVSession/AvCastPickerForCall/entry/src/main/resources/rawfile/test1.wav" desc="Provided by the avsession"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Media/AVSession/AvCastPickerForCall/AppScope/resources/base/media/app_icon.png" desc="Provided by the avsession"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Media/AVSession/AvCastPickerForCall/entry/src/ohosTest/resources/base/media/icon.png" desc="Provided by the avsession"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Media/AVSession/AvCastPickerForCall/screenshots/device/DefaultPicker.jpeg" desc="Provided by the avsession"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Media/AVSession/AvCastPickerForCall/screenshots/device/Index.jpeg" desc="Provided by the avsession"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Media/AVSession/AvCastPickerForCall/screenshots/device/CustomPicker.jpeg" desc="Provided by the avsession"/>
|
||||
<filteritem type="filepath" name="ability/DistributedMusicPlayer/entry/src/main/resources/rawfile/Homey.mp3" desc="Provided by the UX team."/>
|
||||
<filteritem type="filepath" name="ability/DistributedMusicPlayer/entry/src/main/resources/rawfile/Technology.mp3" desc="Provided by the UX team."/>
|
||||
<filteritem type="filepath" name="UI/JsAnimation/entry/src/main/js/default/common/animator/show.mp4" desc="Provided by the UX team."/>
|
||||
@ -142,6 +150,8 @@ Note:If the text contains special characters, please escape them according to th
|
||||
<filteritem type="filepath" name="code/Solutions/Media/MultiMedia/lib/CameraPage-1.0.0.tgz" desc="Provided by code/Solutions/Media/MultiMedia"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Media/AVRecorder/lib/VideoRecorder-1.0.0.tgz" desc="Provided by code/BasicFeature/Media/AVRecorder"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/FileManagement/FileManager/lib/MyPhoneFilePage-1.0.0.tgz" desc="Provided by code/BasicFeature/FileManagement/FileManager"/>
|
||||
<filteritem type="filepath" name="code\BasicFeature/FileManagement/MediaCollections/screenshots/devices/drm_play_page.png" desc="Provided by code/BasicFeature/FileManagement/FileManager"/>
|
||||
<filteritem type="filepath" name="code\BasicFeature/FileManagement/MediaCollections/screenshots/devices/drm_playlist.png" desc="Provided by code/BasicFeature/FileManagement/FileManager"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Connectivity/VPN/lib/VPNFoundation-1.0.0.tgz" desc="Provided by code/BasicFeature/Connectivity/VPN"/>
|
||||
<filteritem type="filepath" name="code/UI/ArkTsComponentCollection/ComponentCollection/lib/DragEvent-1.0.0.tgz" desc="Provided by code/UI/ArkTsComponentCollection/ComponentCollection"/>
|
||||
<filteritem type="filepath" name="code/LaunguageBaseClassLibrary/LanguageBaseClassLibrary/lib/ProcessMessage-1.0.0.tgz" desc="Provided by code/LaunguageBaseClassLibrary/LanguageBaseClassLibrary"/>
|
||||
@ -644,6 +654,14 @@ Note:If the text contains special characters, please escape them according to th
|
||||
<filteritem type="filepath" name="code/SystemFeature/WindowManagement/WindowManage/screenshots/devices/startAbility.png" desc="Provided by code/SystemFeature/WindowManagement/WindowManage"/>
|
||||
<filteritem type="filepath" name="code/SystemFeature/WindowManagement/WindowManage/screenshots/devices/subWindow.png" desc="Provided by code/SystemFeature/WindowManagement/WindowManage"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Native/Audio/entry/src/main/resources/base/media/pic_Audiovivid.png" desc="Provided by code/BasicFeature/Native/Audio"/>
|
||||
<filteritem type="filepath" name="code/SystemFeature/ApplicationModels/EmbeddedUIExtensionAbility/entry/src/ohosTest/resources/base/media/icon.png" desc="Provided by code/SystemFeature/ApplicationModels/EmbeddedUIExtensionAbility"/>
|
||||
<filteritem type="filepath" name="code/SystemFeature/ApplicationModels/EmbeddedUIExtensionAbility/screenshots/main.png" desc="Provided by code/SystemFeature/ApplicationModels/EmbeddedUIExtensionAbility"/>
|
||||
<filteritem type="filepath" name="code/SystemFeature/ApplicationModels/EmbeddedUIExtensionAbility/AppScope/resources/base/media/app_icon.png" desc="Provided by code/SystemFeature/ApplicationModels/EmbeddedUIExtensionAbility"/>
|
||||
<filteritem type="filepath" name="code/SystemFeature/ApplicationModels/EmbeddedUIExtensionAbility/entry/src/main/resources/base/media/icon.png" desc="Provided by code/SystemFeature/ApplicationModels/EmbeddedUIExtensionAbility"/>
|
||||
<filteritem type="filepath" name="code/SystemFeature/ApplicationModels/UIExtensionAbility/screenshots/main.png" desc="Provided by code/SystemFeature/ApplicationModels/UIExtensionAbility"/>
|
||||
<filteritem type="filepath" name="code/SystemFeature/ApplicationModels/UIExtensionAbility/AppScope/resources/base/media/app_icon.png" desc="Provided by code/SystemFeature/ApplicationModels/UIExtensionAbility"/>
|
||||
<filteritem type="filepath" name="code/SystemFeature/ApplicationModels/UIExtensionAbility/entry/src/main/resources/base/media/icon.png" desc="Provided by code/SystemFeature/ApplicationModels/UIExtensionAbility"/>
|
||||
<filteritem type="filepath" name="code/SystemFeature/ApplicationModels/UIExtensionAbility/entry/src/ohosTest/resources/base/media/icon.png" desc="Provided by code/SystemFeature/ApplicationModels/UIExtensionAbility"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Native/Audio/screenshots/device/AudioVivid.jpg" desc="Provided by code/BasicFeature/Native/Audio"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Native/Audio/screenshots/device/index.jpg" desc="Provided by code/BasicFeature/Native/Audio"/>
|
||||
<filteritem type="filepath" name="code/Project/HapBuild/compile-tool/tool/sign_tool/OpenHarmonyProfileDebug.pem" desc="Provided by code/Project/HapBuild"/>
|
||||
@ -659,6 +677,7 @@ Note:If the text contains special characters, please escape them according to th
|
||||
<filteritem type="filepath" name="code/BasicFeature/Notification/CustomCommonEvent/lib/CardEvent-1.0.0.hap" desc="Provided by code/BasicFeature/Notification/CustomCommonEvent"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Ads/OAIDSample/lib/ResetOAID-1.0.0.hap" desc="Provided by code/BasicFeature/Ads/OAIDSample"/>
|
||||
<filteritem type="filepath" name="code/Solutions/Tools/FlipClock/lib/Brightness-1.0.0.hap" desc="Provided by code/Solutions/Tools/FlipClock"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Media/VideoPlay/entry/src/main/resources/rawfile/test.mp4" desc="Provided by code/BasicFeature/Media/VideoPlay"/>
|
||||
|
||||
<filteritem type="filepath" name="code/BasicFeature/DeviceManagement/Sensor/Capi/entry/src/main/resources/base/media/compass.png" desc="Provided by code/BasicFeature/DeviceManagement/Sensor"/>
|
||||
<filteritem type="filepath" name=" code/BasicFeature/DeviceManagement/Vibrator/CustomHaptic/entry/src/main/resources/base/media/select.png" desc="Provided by code/BasicFeature/DeviceManagement/Vibrator"/>
|
||||
@ -718,6 +737,8 @@ Note:If the text contains special characters, please escape them according to th
|
||||
<filteritem type="filepath" name="code/BasicFeature/DataManagement/pasteboard/AppScope/resources/base/media/app_icon.png" desc="Provided by code/BasicFeature/DataManagement/pasteboard"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/DataManagement/pasteboard/entry/src/main/resources/base/media/icon.png" desc="Provided by code/BasicFeature/DataManagement/pasteboard"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/DataManagement/pasteboard/screenshots/first.png" desc="Provided by code/BasicFeature/DataManagement/pasteboard"/>
|
||||
<filteritem type="filepath" name="code/Solutions/InputMethod/KikaInputMethod/entry/src/main/resources/base/media/startIcon.png" desc="Provided by code/Solutions/InputMethod/KikaInputMethod"/>
|
||||
<filteritem type="filepath" name="code/Solutions/InputMethod/KikaInputMethod/screenshots/devices/preview.jpg" desc="Provided by code/Solutions/InputMethod/KikaInputMethod"/>
|
||||
</filefilter>
|
||||
<filefilter name="defaultPolicyFilter" desc="License文件头校验策略的过滤条件" >
|
||||
<filteritem type="filename" name="oh-package.json5" desc="OpenHarmony工程编译入口脚本,无需添加License头"/>
|
||||
@ -763,6 +784,7 @@ Note:If the text contains special characters, please escape them according to th
|
||||
<filteritem type="filepath" name="code/BasicFeature/Graphics/Graphics3d/entry/src/main/resources/rawfile/shaders/custom_shader/custom_material_sample.frag.spv.gles" desc="二进制文件,无需添加版权头"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Graphics/Graphics3d/entry/src/main/resources/rawfile/shaders/custom_shader/custom_material_sample.frag.spv.pre" desc="二进制文件,无需添加版权头"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Graphics/Graphics3d/entry/src/main/resources/rawfile/shaders/custom_shader/custom_material_sample.shader" desc="二进制文件,无需添加版权头"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Media/VideoPlay/entry/src/main/resources/rawfile/test1.srt" desc="Demo外挂字幕文件,无需添加版权头"/>
|
||||
</filefilter>
|
||||
<filefilter name="copyrightPolicyFilter" desc="Copyright文件头校验策略的过滤条件" >
|
||||
<filteritem type="filename" name="oh-package.json5" desc="OpenHarmony工程编译入口脚本,无需添加版权头"/>
|
||||
@ -808,6 +830,7 @@ Note:If the text contains special characters, please escape them according to th
|
||||
<filteritem type="filepath" name="code/BasicFeature/Graphics/Graphics3d/entry/src/main/resources/rawfile/shaders/custom_shader/custom_material_sample.frag.spv.gles" desc="二进制文件,无需添加版权头"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Graphics/Graphics3d/entry/src/main/resources/rawfile/shaders/custom_shader/custom_material_sample.frag.spv.pre" desc="二进制文件,无需添加版权头"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Graphics/Graphics3d/entry/src/main/resources/rawfile/shaders/custom_shader/custom_material_sample.shader" desc="二进制文件,无需添加版权头"/>
|
||||
<filteritem type="filepath" name="code/BasicFeature/Media/VideoPlay/entry/src/main/resources/rawfile/test1.srt" desc="Demo外挂字幕文件,无需添加版权头"/>
|
||||
</filefilter>
|
||||
</filefilterlist>
|
||||
</oatconfig>
|
||||
|
@ -1,5 +1,6 @@
|
||||
/node_modules
|
||||
/local.properties
|
||||
/oh_modules
|
||||
/.idea
|
||||
**/build
|
||||
/.hvigor
|
||||
|
@ -1,25 +1,34 @@
|
||||
# 媒体管理合集
|
||||
|
||||
### 介绍
|
||||
### 介绍
|
||||
|
||||
#### 具体功能
|
||||
|
||||
1. 网络流播放能力
|
||||
2. 音视频播控能力
|
||||
3. 音量调节能力
|
||||
4. DRM解密播放能力
|
||||
|
||||
### 效果预览
|
||||
|
||||
|主页|音频| 视频 | 重命名 |
|
||||
|--------------------------------|--------------------------------|------------------------------------|-------------------------------------|
|
||||
|![](screenshots/devices/index.png) |![](screenshots/devices/audio.png)| ![](screenshots/devices/video.png) | ![](screenshots/devices/rename.png) |
|
||||
| 主页 | 音频 | 视频 | 重命名 |
|
||||
|--------------------------------------------|-------------------------------------------|------------------------------------|-------------------------------------|
|
||||
| ![](screenshots/devices/index.png) | ![](screenshots/devices/audio.png) | ![](screenshots/devices/video.png) | ![](screenshots/devices/rename.png) |
|
||||
| drm播放列表 | drm播放页 |
|
||||
| ![](screenshots/devices/drm_play_page.png) | ![](screenshots/devices/drm_playlist.png) |
|
||||
|
||||
|
||||
使用说明
|
||||
|
||||
1. 启动应用,点击音频或视频可以查看本地音视频资源。若本地没有音视频资源,可以push视频到本地媒体库路径,视频路径(storage/media/100/local/files/Videos) 音频路径(storage/media/100/local/files/Audios),需要先hdc进入storage/media/100/local/files路径下查看是否有Videos/Audios文件夹,没有则需要mkdir Videos/Audios创建文件夹后push音视频资源。
|
||||
1. 启动应用,选择离线播放,点击音频或视频可以查看本地音视频资源。若本地没有音视频资源,可以push视频到本地媒体库路径,视频路径(storage/media/100/local/files/Videos) 音频路径(storage/media/100/local/files/Audios),需要先hdc进入storage/media/100/local/files路径下查看是否有Videos/Audios文件夹,没有则需要mkdir Videos/Audios创建文件夹后push音视频资源。
|
||||
2. 进入首页,可以通过输入网络地址或点击音频,视频进行播放音视频。
|
||||
3. 音视频播放后,对于播控按键显示,点击播放、暂停可以播放、暂停音视频。
|
||||
4. 音视频列表左滑可以开启重命名、删除图标,点击对应图标可以进行音视频的重命名和删除。
|
||||
3. 音视频播放后,对于播控按键显示,点击播放、暂停可以播放、暂停音视频,点击循环图标可以在单曲循环和列表循环进行切换。
|
||||
4. 点击下一曲、上一曲可以切换音视频,拖动seek可以跳到指定位置播放,点击详细信息可以查看当前音视频信息,视频点击倍速可以开启倍速播放。
|
||||
5. 音视频列表左滑可以开启重命名、删除图标,点击对应图标可以进行音视频的重命名和删除。
|
||||
6. 返回应用首页,选择drm在线播放,进入播放列表。
|
||||
7. 播放列表中都是drm加密片源,在列表中选择一条片源点击,进入播放页后底层检测到加密片源,会自动执行drm解密流程,进行在线播放。
|
||||
8. 在播放页,可以进行拖动进度条seek、暂停、倍速、循环、跳转第一帧、跳转最后一帧等操作。
|
||||
9. 返回播放列表,点击钥匙按钮后再次播放,对应片源会走drm离线license播放,使用与在线相同。
|
||||
|
||||
### 工程目录
|
||||
```
|
||||
@ -91,6 +100,11 @@ entry/src/main/ets/
|
||||
* 源码链接:[MainAbility.ts](entry/src/main/ets/MainAbility/MainAbility.ts),[RenameDialog.ets](entry/src/main/ets/common/RenameDialog.ets)
|
||||
* 接口参考:[@ohos.display](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-as/js-apis-display.md)
|
||||
|
||||
* Drm解密模块模块
|
||||
* 使用Drm接口对加密视频进行解密播放,支持HLS+TS格式,支持离线license、Renew、过期事件上报。
|
||||
* 源码链接:[DrmController.ets ](entry/src/main/ets/model/DrmController.ets)
|
||||
* 接口参考:@ohos.multimedia.drm
|
||||
|
||||
### 相关权限
|
||||
|
||||
1.读取公共媒体文件权限:[ohos.permission.READ_MEDIA](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/AccessToken/permissions-for-all.md#ohospermissionread_media)
|
||||
@ -105,9 +119,13 @@ entry/src/main/ets/
|
||||
|
||||
1.本示例仅支持标准系统上运行,支持设备:RK3568。
|
||||
|
||||
2.本示例已适配API version 10版本SDK,本示例涉及使用系统接口:getXComponentSurfaceId(),需要手动替换Full SDK才能编译通过,具体操作可参考[替换指南](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/faqs/full-sdk-switch-guide.md)。
|
||||
2.本示例已适配API version 12版本SDK,版本号:5.0.0.22。
|
||||
|
||||
3.本示例需要使用DevEco Studio 3.1 Beta2 (Build Version: 3.1.0.400 构建 2023年4月7日)及以上版本才可编译运行。
|
||||
3.本示例需要使用DevEco Studio NEXT Developer Beta1 (Build Version: 5.0.1.100 构建 2024年3月25日)及以上版本才可编译运行。
|
||||
|
||||
4.本示例涉及使用系统接口:getXComponentSurfaceId(),需要手动替换Full SDK才能编译通过,具体操作可参考[替换指南](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/faqs/full-sdk-switch-guide.md)。
|
||||
|
||||
5.本示例DRM播放需要IP白名单才可以运行,设备需要含DCM证书,否则无法播放DRM片源。
|
||||
|
||||
### 下载
|
||||
如需单独下载本工程,执行如下命令:
|
||||
@ -118,4 +136,4 @@ git config core.sparsecheckout true
|
||||
echo code/BasicFeature/FileManagement/MediaCollections/ > .git/info/sparse-checkout
|
||||
git remote add origin https://gitee.com/openharmony/applications_app_samples.git
|
||||
git pull origin master
|
||||
```
|
||||
```
|
||||
|
@ -20,8 +20,14 @@
|
||||
{
|
||||
"name": "default",
|
||||
"signingConfig": "default",
|
||||
"compileSdkVersion": 10,
|
||||
"compatibleSdkVersion": 10
|
||||
//指定OpenHarmony应用/服务编译时的版本
|
||||
"compileSdkVersion": 12,
|
||||
//指定OpenHarmony应用/服务兼容的最低版本。版本号需改为10,注意使用英文.和()
|
||||
"compatibleSdkVersion": 12,
|
||||
//指定OpenHarmony应用/服务目标版本。若没有设置,默认为compatibleSdkVersion
|
||||
"targetSdkVersion": 12,
|
||||
//指定为OpenHarmony
|
||||
"runtimeOS": "OpenHarmony"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -20,20 +20,20 @@ export default class DrmConstants {
|
||||
/**
|
||||
* WisePlay MediaKeySystem的名称
|
||||
*/
|
||||
static readonly WISEPLAY_DRM_UUID: string = 'com.wiseplay.drm'
|
||||
static readonly WISEPLAY_DRM_NAME: string = 'com.wiseplay.drm'
|
||||
/**
|
||||
* ClearPlay MediaKeySystem的名称
|
||||
*/
|
||||
static readonly CLEAR_PLAY_DRM_UUID: string = 'com.clearplay.drm'
|
||||
static readonly CLEAR_PLAY_DRM_NAME: string = 'com.clearplay.drm'
|
||||
}
|
||||
|
||||
/**
|
||||
* KeySession 回调事件
|
||||
*/
|
||||
export enum KeySessionEvents {
|
||||
KEY_NEEDED = 'keyNeeded',
|
||||
KEY_REQUIRED = 'keyRequired',
|
||||
KEY_EXPIRED = 'keyExpired',
|
||||
VENDOR_DEFINED = 'vendorDefined',
|
||||
EXPIRATION_UPDATED = 'expirationUpdated',
|
||||
KEYS_CHANGED = 'keyChanged',
|
||||
EXPIRATION_UPDATE = 'expirationUpdate',
|
||||
KEYS_CHANGED = 'keysChange',
|
||||
}
|
@ -18,8 +18,9 @@ import { BusinessError } from '@ohos.base'
|
||||
import media from '@ohos.multimedia.media'
|
||||
import audio from '@ohos.multimedia.audio'
|
||||
import common from '@ohos.app.ability.common'
|
||||
import drm from '@ohos.multimedia.drm'
|
||||
import Logger from '../model/Logger'
|
||||
import DrmController, { drmInfo } from './DrmController'
|
||||
import DrmController from './DrmController'
|
||||
|
||||
const TAG = 'AVPlayer'
|
||||
|
||||
@ -49,6 +50,8 @@ export class AVPlayer {
|
||||
}
|
||||
private callbackErrorUpdate: (error: string) => void = (error: string) => {
|
||||
}
|
||||
private callbackLicenseInfoUpdate: (licenseInfo: string) => void = (licenseInfo: string) => {
|
||||
}
|
||||
private fd: number = 0
|
||||
private fileDescriptor: media.AVFileDescriptor = {} as media.AVFileDescriptor
|
||||
private isCreate: boolean = false
|
||||
@ -83,6 +86,10 @@ export class AVPlayer {
|
||||
this.callbackErrorUpdate = func
|
||||
}
|
||||
|
||||
setLicenseInfoCallback(func: (licenseInfo: string) => void) {
|
||||
this.callbackLicenseInfoUpdate = func
|
||||
}
|
||||
|
||||
async prev(surfaceId: string) {
|
||||
this.isHttp = false
|
||||
if (this.fileIndex > 0) {
|
||||
@ -162,8 +169,7 @@ export class AVPlayer {
|
||||
}
|
||||
|
||||
async httpInit(url: string, surfaceId?: string) {
|
||||
this.isHttp = true;
|
||||
this.isVideo = true;
|
||||
this.isHttp = true
|
||||
if (surfaceId) {
|
||||
this.surfaceId = surfaceId.toString()
|
||||
Logger.info(TAG, `surfaceId success: ${surfaceId}`)
|
||||
@ -209,7 +215,7 @@ export class AVPlayer {
|
||||
|
||||
async init(resourceAddress: mediaLibrary.FileAsset, surfaceId?: string) {
|
||||
this.isHttp = false
|
||||
Logger.info(TAG, `init state`)
|
||||
Logger.info(TAG, 'init state')
|
||||
this.isVideo = false
|
||||
// 判断是否传入surfaceID
|
||||
if (surfaceId) {
|
||||
@ -230,7 +236,7 @@ export class AVPlayer {
|
||||
Logger.info(TAG, `fd success: ${fd}`)
|
||||
this.fd = fd
|
||||
if (this.resourceAddress.mediaType == mediaLibrary.MediaType.AUDIO) {
|
||||
Logger.info(TAG, `AUDIO success`)
|
||||
Logger.info(TAG, 'AUDIO success')
|
||||
this.getFileIndex(this.audioData)
|
||||
this.mediaDataLen = this.audioData.length
|
||||
} else if (this.resourceAddress.mediaType == mediaLibrary.MediaType.VIDEO) {
|
||||
@ -264,9 +270,8 @@ export class AVPlayer {
|
||||
// 视频信息上报函数
|
||||
async setSourceInfo(): Promise<void> {
|
||||
// drmInfo 更新回调
|
||||
this.media.on('drmInfoUpdate', (drmInfo: Object[]) => {
|
||||
const drmInfoArr: drmInfo[] = drmInfo as drmInfo[]
|
||||
this.drmPlayProcess(drmInfoArr)
|
||||
this.media.on('mediaKeySystemInfoUpdate', (mediaKeySystemInfo: drm.MediaKeySystemInfo[]) => {
|
||||
this.drmPlayProcess(mediaKeySystemInfo)
|
||||
})
|
||||
// 时间上报函数
|
||||
this.media.on('timeUpdate', (time: number) => {
|
||||
@ -324,6 +329,8 @@ export class AVPlayer {
|
||||
this.media.on('error', (error: BusinessError) => {
|
||||
Logger.error(TAG, 'error happened,message is :' + error.message)
|
||||
this.callbackErrorUpdate('Error ' + error.code + ' - ' + error.message)
|
||||
// 当error上报时自动播放下一个视频或者音频
|
||||
this.next(this.surfaceId)
|
||||
})
|
||||
}
|
||||
|
||||
@ -472,7 +479,7 @@ export class AVPlayer {
|
||||
* DRM播放流程启动
|
||||
* @param drmInfoArr
|
||||
*/
|
||||
async drmPlayProcess(drmInfoArr: drmInfo[]) {
|
||||
async drmPlayProcess(drmInfoArr: drm.MediaKeySystemInfo[]) {
|
||||
if (this.drmInfoFirstUpdate === false) {
|
||||
this.drmInfoFirstUpdate = true
|
||||
Logger.info(TAG, 'player has received drmInfo signal: ' + JSON.stringify(drmInfoArr))
|
||||
@ -487,16 +494,29 @@ export class AVPlayer {
|
||||
|
||||
// --- 2 --- 创建drm实例及会话
|
||||
this.drmController.createDrmSystem()
|
||||
// 注册过期release回调
|
||||
this.drmController.getReleaseCallback(() => {
|
||||
this.release()
|
||||
this.callbackErrorUpdate('Failed to play the video. \n The license has expired.')
|
||||
})
|
||||
|
||||
// --- 3 --- 生成license请求;发送license到云端获取响应,将响应交给底层
|
||||
let isSuccess: boolean = await this.drmController.executeLicenseProcess(this.drmUrl, this.drmLicenseUrl, drmInfoArr, AppStorage.get('licenseType'))
|
||||
let licenseType: number = AppStorage.get('licenseType') as number
|
||||
let isSuccess: boolean = await this.drmController
|
||||
.executeLicenseProcess(this.drmUrl, this.drmLicenseUrl, drmInfoArr, licenseType)
|
||||
if (!isSuccess) {
|
||||
Logger.error(TAG, 'Failed to process the license, Release Playback Resources.')
|
||||
this.release()
|
||||
this.callbackErrorUpdate('Failed to play the video.')
|
||||
return
|
||||
}
|
||||
|
||||
// --- 4 --- 设置加密配置
|
||||
this.drmController.bindInstance(this.media)
|
||||
|
||||
// --- 5 --- 获取license info
|
||||
const licenseInfo: string = this.drmController.getLicenseInfo()
|
||||
this.callbackLicenseInfoUpdate(licenseInfo)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,30 +21,30 @@ import TypeConversion from './TypeConversion'
|
||||
import HttpUtil from './HttpUtil'
|
||||
import KVManagerUtil from './KVManagerUtil'
|
||||
|
||||
/**
|
||||
* drmInfo 接口类型
|
||||
*/
|
||||
export interface drmInfo {
|
||||
uuid: string,
|
||||
pssh: Uint8Array,
|
||||
}
|
||||
|
||||
export default class DrmController {
|
||||
private TAG: string = 'DrmController'
|
||||
private mediaKeySystem: drm.MediaKeySystem = {} as drm.MediaKeySystem
|
||||
private mediaKeySession: drm.MediaKeySession = {} as drm.MediaKeySession
|
||||
private uuid: string = ''
|
||||
private drmInfoArr: drm.MediaKeySystemInfo[] = []
|
||||
private solutionName: string = ''
|
||||
private licenseInfo: string = ''
|
||||
private drmUrl: string = ''
|
||||
private licenseType: number = 1
|
||||
private svp: boolean = false
|
||||
private releaseCallback: () => void = () => {
|
||||
}
|
||||
private licenseInfoCallback: () => void = () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断系统是否支持 drm 能力
|
||||
*/
|
||||
isDrmSupported(): boolean {
|
||||
if (drm.isMediaKeySystemSupported(DrmConstants.WISEPLAY_DRM_UUID)) {
|
||||
this.uuid = DrmConstants.WISEPLAY_DRM_UUID
|
||||
if (drm.isMediaKeySystemSupported(DrmConstants.WISEPLAY_DRM_NAME)) {
|
||||
this.solutionName = DrmConstants.WISEPLAY_DRM_NAME
|
||||
return true
|
||||
} else if (drm.isMediaKeySystemSupported(DrmConstants.CLEAR_PLAY_DRM_UUID)) {
|
||||
this.uuid = DrmConstants.CLEAR_PLAY_DRM_UUID
|
||||
} else if (drm.isMediaKeySystemSupported(DrmConstants.CLEAR_PLAY_DRM_NAME)) {
|
||||
this.solutionName = DrmConstants.CLEAR_PLAY_DRM_NAME
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -54,7 +54,7 @@ export default class DrmController {
|
||||
* 创建 MediaKeySystem、KeySession 实例
|
||||
*/
|
||||
createDrmSystem() {
|
||||
this.mediaKeySystem = drm.createMediaKeySystem(this.uuid)
|
||||
this.mediaKeySystem = drm.createMediaKeySystem(this.solutionName)
|
||||
if (!this.mediaKeySystem) {
|
||||
Logger.error(this.TAG, 'getMediaKeySystem fail!')
|
||||
return
|
||||
@ -62,10 +62,12 @@ export default class DrmController {
|
||||
Logger.info(this.TAG, 'MediaKeySystem has been created.')
|
||||
|
||||
// 若无证书,此接口会返回失败
|
||||
this.mediaKeySession = this.mediaKeySystem.createMediaKeySession(drm.SecurityLevel.SECURITY_LEVEL_SW_CRYPTO)
|
||||
this.mediaKeySession = this.mediaKeySystem
|
||||
.createMediaKeySession(drm.ContentProtectionLevel.CONTENT_PROTECTION_LEVEL_HW_CRYPTO)
|
||||
if (this.mediaKeySession) {
|
||||
Logger.info(this.TAG, 'mediaKeySession has been created.')
|
||||
this.svp = this.mediaKeySession.requireSecureDecoderModule('video/avc')
|
||||
Logger.info(this.TAG, ' this.svp: ' + this.svp)
|
||||
this.setDrmCallback()
|
||||
} else {
|
||||
Logger.error(this.TAG, 'createKeySession fail!')
|
||||
@ -78,43 +80,44 @@ export default class DrmController {
|
||||
*/
|
||||
setDrmCallback() {
|
||||
if (!this.mediaKeySession) {
|
||||
Logger.error(this.TAG, 'setDrmCallback: this.mediaKeySession is undefined!')
|
||||
Logger.error(this.TAG, 'this.mediaKeySession is undefined!')
|
||||
return
|
||||
}
|
||||
Logger.info(this.TAG, 'Start to set callback')
|
||||
// renew上报 重新获取license
|
||||
this.mediaKeySession.on(KeySessionEvents.KEY_NEEDED, (eventInfo: drm.EventInfo) => {
|
||||
Logger.info(this.TAG, 'keyNeeded callback success.' + JSON.stringify(eventInfo))
|
||||
this.mediaKeySession.on(KeySessionEvents.KEY_REQUIRED, async (eventInfo: drm.EventInfo) => {
|
||||
Logger.info(this.TAG, 'keyRequired callback success.')
|
||||
Logger.info(this.TAG, `info: ${TypeConversion.byteToString(eventInfo.info)}, extraInfo: ${eventInfo.extraInfo}`)
|
||||
|
||||
this.renew()
|
||||
this.getLicenseInfo()
|
||||
})
|
||||
// 密钥过期上报 播放失败
|
||||
this.mediaKeySession.on(KeySessionEvents.KEY_EXPIRED, (eventInfo: drm.EventInfo) => {
|
||||
Logger.info(this.TAG, 'keyExpired callback success.' + JSON.stringify(eventInfo))
|
||||
Logger.info(this.TAG, 'keyExpired callback success.')
|
||||
Logger.info(this.TAG, `info: ${TypeConversion.byteToString(eventInfo.info)}, extraInfo: ${eventInfo.extraInfo}`)
|
||||
|
||||
this.getLicenseInfo()
|
||||
this.releaseCallback()
|
||||
})
|
||||
// license过期时长上报 license响应交给底层时触发
|
||||
this.mediaKeySession.on(KeySessionEvents.EXPIRATION_UPDATED, (eventInfo: drm.EventInfo) => {
|
||||
Logger.info(this.TAG, 'expirationUpdated callback success.' + JSON.stringify(eventInfo))
|
||||
})
|
||||
|
||||
this.mediaKeySession.on(KeySessionEvents.VENDOR_DEFINED, (eventInfo: drm.EventInfo) => {
|
||||
Logger.info(this.TAG, 'vendorDefined callback success.' + JSON.stringify(eventInfo))
|
||||
})
|
||||
// keyId
|
||||
this.mediaKeySession.on(KeySessionEvents.KEYS_CHANGED, (eventInfo: drm.EventInfo) => {
|
||||
Logger.info(this.TAG, 'keyChanged callback success.' + JSON.stringify(eventInfo))
|
||||
this.mediaKeySession.on(KeySessionEvents.EXPIRATION_UPDATE, (eventInfo: drm.EventInfo) => {
|
||||
Logger.info(this.TAG, 'expirationUpdate callback success.')
|
||||
Logger.info(this.TAG, `info: ${TypeConversion.byteToString(eventInfo.info)}, extraInfo: ${eventInfo.extraInfo}`)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* todo 注销 drm 相关回调函数
|
||||
* 注销 drm 相关回调函数
|
||||
*/
|
||||
destroyDrmCallback() {
|
||||
if (!this.mediaKeySession) {
|
||||
Logger.error(this.TAG, 'destroyDrmCallback: this.mediaKeySession is undefined!')
|
||||
Logger.error(this.TAG, 'this.mediaKeySession is undefined!')
|
||||
return
|
||||
}
|
||||
this.mediaKeySession.off(KeySessionEvents.KEY_NEEDED)
|
||||
this.mediaKeySession.off(KeySessionEvents.KEY_REQUIRED)
|
||||
this.mediaKeySession.off(KeySessionEvents.KEY_EXPIRED)
|
||||
this.mediaKeySession.off(KeySessionEvents.EXPIRATION_UPDATED)
|
||||
this.mediaKeySession.off(KeySessionEvents.EXPIRATION_UPDATE)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,16 +129,17 @@ export default class DrmController {
|
||||
return
|
||||
}
|
||||
// Provision Request
|
||||
let provisionRequestData: drm.ProvisionRequest = this.mediaKeySystem.generateKeySystemRequest()
|
||||
let provisionRequestStr: string = TypeConversion.byteToString(provisionRequestData.mData)
|
||||
let provisionRequestData: drm.ProvisionRequest = await this.mediaKeySystem.generateKeySystemRequest()
|
||||
let provisionRequestStr: string = TypeConversion.byteToString(provisionRequestData.data)
|
||||
Logger.info(this.TAG, 'ProvisionRequest[' + provisionRequestStr.length + ']:' + provisionRequestStr)
|
||||
|
||||
// Provision Response
|
||||
try {
|
||||
let provisionResponseStr: string = await HttpUtil.getDrmResponse("https://drmkit.hwcloudtest.cn:8080/provision/v1/wiseplay", provisionRequestStr)
|
||||
let provisionResponseStr: string = await HttpUtil.getDrmResponse('https://drmkit.hwcloudtest.cn:8080/provision/v1/wiseplay', provisionRequestStr)
|
||||
Logger.info(this.TAG, 'provisionResponse[' + provisionResponseStr.length + ']:' + provisionResponseStr)
|
||||
let provisionRequestByte: Uint8Array = TypeConversion.stringToByte(provisionResponseStr)
|
||||
this.mediaKeySystem.processKeySystemResponse(provisionRequestByte)
|
||||
await this.mediaKeySystem.processKeySystemResponse(provisionRequestByte)
|
||||
Logger.info(this.TAG, 'get provision success.')
|
||||
} catch (e) {
|
||||
Logger.info(this.TAG, 'error [' + JSON.stringify(e) + ']')
|
||||
}
|
||||
@ -146,47 +150,63 @@ export default class DrmController {
|
||||
* @param drmInfoArr drm信息参数
|
||||
* @param drmUrl 片源地址
|
||||
* @param drmLicenseUrl license请求地址
|
||||
* @param licenseType license类型, 0 在线, 1 离线
|
||||
* @param licenseType license类型, 1 在线, 0 离线
|
||||
* @returns
|
||||
*/
|
||||
async getLicense(drmInfoArr: drmInfo[], drmUrl: string, drmLicenseUrl: string, licenseType: number): Promise<boolean> {
|
||||
async getLicense(drmInfoArr: drm.MediaKeySystemInfo[], drmUrl: string, drmLicenseUrl: string, licenseType: number): Promise<boolean> {
|
||||
if (!this.mediaKeySession) {
|
||||
Logger.error(this.TAG, 'generateLicenseRequest: this.mediaKeySession is undefined!')
|
||||
Logger.error(this.TAG, 'this.mediaKeySession is undefined!')
|
||||
return false
|
||||
}
|
||||
|
||||
let isSuccess: boolean = false
|
||||
let isSuccess: boolean = true
|
||||
for (let i = 0; i < drmInfoArr.length; i++) {
|
||||
// license Request
|
||||
const optionalData: drm.OptionalData[] = [{
|
||||
name: "optionalDataName",
|
||||
value: "optionalDataValue"
|
||||
}]
|
||||
Logger.info(this.TAG, 'drmInfoArr - uuid: ' + drmInfoArr[i].uuid)
|
||||
Logger.info(this.TAG, 'drmInfoArr - pssh: ' + drmInfoArr[i].pssh)
|
||||
|
||||
// license Request
|
||||
const optionsData: drm.OptionsData[] = [{
|
||||
name: 'optionalDataName',
|
||||
value: 'optionalDataValue'
|
||||
}]
|
||||
const uint8pssh: Uint8Array = new Uint8Array(drmInfoArr[i].pssh)
|
||||
const licenseRequestData: drm.LicenseRequest = this.mediaKeySession.generateLicenseRequest('video/mp4', uint8pssh, licenseType, optionalData)
|
||||
let licenseRequestStr: string = TypeConversion.byteToString(licenseRequestData.mData)
|
||||
const licenseRequestData: drm.MediaKeyRequest = await this.mediaKeySession.generateMediaKeyRequest('video/mp4', uint8pssh, licenseType, optionsData)
|
||||
let licenseRequestStr: string = TypeConversion.byteToString(licenseRequestData.data)
|
||||
Logger.info(this.TAG, 'licenseRequestStr[' + licenseRequestStr.length + ']:' + licenseRequestStr)
|
||||
|
||||
// license Response
|
||||
let licenseResponseStr: string = await HttpUtil.getDrmResponse(drmLicenseUrl, licenseRequestStr)
|
||||
Logger.info(this.TAG, 'licenseResponseStr[' + licenseResponseStr.length + ']:' + licenseResponseStr)
|
||||
// 请求地址或传参错误时返回的result不是正确的license response
|
||||
const licenseResponseObj: Record<string, Object> = JSON.parse(licenseResponseStr)
|
||||
if (!licenseResponseObj.certificateChain) {
|
||||
Logger.error(this.TAG, 'licenseResponse msg: ' + licenseResponseObj?.msg)
|
||||
|
||||
// 请求异常
|
||||
if (licenseResponseStr === 'defaultStr') {
|
||||
isSuccess = false
|
||||
break
|
||||
} else {
|
||||
// 请求地址或传参错误时返回的result不是正确的license response
|
||||
const licenseResponseObj: Record<string, Object> = JSON.parse(licenseResponseStr)
|
||||
if (!licenseResponseObj.certificateChain) {
|
||||
Logger.error(this.TAG, 'licenseResponse msg: ' + licenseResponseObj?.msg)
|
||||
isSuccess = false
|
||||
}
|
||||
}
|
||||
|
||||
let licenseResponseData: Uint8Array = TypeConversion.stringToByte(licenseResponseStr)
|
||||
// 将响应交给底层(在线license返回空字符串,离线license返回keySetId)
|
||||
let KeySetId: Uint8Array = this.mediaKeySession.processLicenseResponse(licenseResponseData)
|
||||
isSuccess = true
|
||||
// 若为离线license则存储起来
|
||||
if (licenseType === 1 && KeySetId) {
|
||||
Logger.info(this.TAG, 'KeySetId is: ' + TypeConversion.byteToString(KeySetId))
|
||||
KVManagerUtil.put(drmUrl, TypeConversion.byteToString(KeySetId))
|
||||
try {
|
||||
let KeySetId: Uint8Array = await this.mediaKeySession.processMediaKeyResponse(licenseResponseData)
|
||||
// 若为离线license则存储起来
|
||||
if (licenseType === 0 && KeySetId) {
|
||||
Logger.info(this.TAG, 'KeySetId is: ' + TypeConversion.byteToString(KeySetId))
|
||||
KVManagerUtil.put(drmUrl, TypeConversion.byteToString(KeySetId))
|
||||
// 查看已下载的离线Id
|
||||
const keyIds: Uint8Array[] = this.mediaKeySystem.getOfflineMediaKeyIds()
|
||||
keyIds.forEach((item: Uint8Array) => {
|
||||
Logger.info(this.TAG, 'Downloaded offline id: ' + TypeConversion.byteToString(item))
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
isSuccess = false
|
||||
Logger.error(this.TAG, `get KeySetId error, message is ${(e as Error).message}`)
|
||||
}
|
||||
}
|
||||
return isSuccess
|
||||
@ -200,30 +220,34 @@ export default class DrmController {
|
||||
* @param licenseType
|
||||
* @returns
|
||||
*/
|
||||
async executeLicenseProcess(drmUrl: string, drmLicenseUrl: string, drmInfoArr: drmInfo[], licenseType: number): Promise<boolean> {
|
||||
async executeLicenseProcess(drmUrl: string, drmLicenseUrl: string, drmInfoArr: drm.MediaKeySystemInfo[], licenseType: number): Promise<boolean> {
|
||||
if (!this.mediaKeySession) {
|
||||
Logger.error(this.TAG, 'executeLicenseProcess: this.mediaKeySession is undefined!')
|
||||
Logger.error(this.TAG, 'this.mediaKeySession is undefined!')
|
||||
return false
|
||||
}
|
||||
Logger.info(this.TAG, `Start to execute license process`)
|
||||
Logger.info(this.TAG, 'Start to execute license process')
|
||||
|
||||
if (licenseType === 0) {
|
||||
Logger.info(this.TAG, `Get onlineLicense`)
|
||||
this.drmUrl = drmUrl
|
||||
this.drmInfoArr = drmInfoArr
|
||||
this.licenseType = licenseType
|
||||
|
||||
if (licenseType === 1) {
|
||||
Logger.info(this.TAG, 'Get onlineLicense')
|
||||
return await this.getLicense(drmInfoArr, drmUrl, drmLicenseUrl, licenseType)
|
||||
} else if (licenseType === 1) {
|
||||
} else if (licenseType === 0) {
|
||||
let keyId: string = await KVManagerUtil.get(drmUrl)
|
||||
if (keyId) { // 已存储过,直接使用离线license进行播放
|
||||
Logger.info(this.TAG, `Offlinelicense exist, keyId is ${keyId}`)
|
||||
this.mediaKeySession.restoreOfflineLicense(TypeConversion.stringToByte(keyId))
|
||||
this.mediaKeySession.restoreOfflineMediaKeys(TypeConversion.stringToByte(keyId))
|
||||
return true
|
||||
} else { // 若没有存储过,则需要请求license
|
||||
Logger.info(this.TAG, `Get offlineLicense`)
|
||||
Logger.info(this.TAG, 'Get offlineLicense')
|
||||
return await this.getLicense(drmInfoArr, drmUrl, drmLicenseUrl, licenseType)
|
||||
}
|
||||
} else {
|
||||
Logger.error(this.TAG, 'LicenseType error')
|
||||
return false
|
||||
}
|
||||
|
||||
Logger.error(this.TAG, `LicenseType error`)
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
@ -232,20 +256,51 @@ export default class DrmController {
|
||||
*/
|
||||
async deleteOfflineLicense(drmUrl: string) {
|
||||
if (!this.mediaKeySession) {
|
||||
Logger.error(this.TAG, 'deleteOfflineLicense: this.mediaKeySession is undefined!')
|
||||
Logger.error(this.TAG, 'this.mediaKeySession is undefined!')
|
||||
return
|
||||
}
|
||||
|
||||
let keyId: string = await KVManagerUtil.get(drmUrl)
|
||||
Logger.info(this.TAG, 'KeyId to be delete is: ' + keyId);
|
||||
Logger.info(this.TAG, 'KeyId to be delete is: ' + keyId)
|
||||
if (keyId) {
|
||||
let offlineLicenseId = new Uint8Array(TypeConversion.stringToByte(keyId));
|
||||
let offlineLicenseStatus: drm.OfflineLicenseStatus = this.mediaKeySystem.getOfflineLicenseStatus(offlineLicenseId);
|
||||
Logger.info(this.TAG, 'Get offlineLicense Status is: ' + offlineLicenseStatus);
|
||||
let offlineLicenseId = new Uint8Array(TypeConversion.stringToByte(keyId))
|
||||
let offlineLicenseStatus: drm.OfflineMediaKeyStatus = this.mediaKeySystem.getOfflineMediaKeyStatus(offlineLicenseId)
|
||||
Logger.info(this.TAG, 'getOfflineMediaKeyStatus: ' + offlineLicenseStatus)
|
||||
|
||||
this.mediaKeySystem.removeOfflineLicense(offlineLicenseId);
|
||||
this.mediaKeySystem.clearOfflineMediaKeys(offlineLicenseId)
|
||||
KVManagerUtil.delete(drmUrl)
|
||||
Logger.info(this.TAG, 'Succeeded in deleting offline license.');
|
||||
Logger.info(this.TAG, 'Delete offline license success.')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* renew 重新获取license
|
||||
*/
|
||||
async renew() {
|
||||
// 获取license信息
|
||||
const mediaKeyStatus: drm.MediaKeyStatus[] = this.mediaKeySession.checkMediaKeyStatus()
|
||||
const licenseInfoMap: Map<string, string> = new Map<string, string>()
|
||||
mediaKeyStatus.forEach((item: drm.MediaKeyStatus) => {
|
||||
licenseInfoMap.set(item.name, item.value)
|
||||
})
|
||||
Logger.info(this.TAG, `checkMediaKeyStatus: ${JSON.stringify(Array.from(licenseInfoMap.entries()))}`)
|
||||
|
||||
// 获取新url请求license
|
||||
let renewAllowed: boolean = licenseInfoMap.has('RenewAllowed') && (licenseInfoMap.get('RenewAllowed') as string) === 'True'
|
||||
if (renewAllowed) {
|
||||
let oldKeyId: string = await KVManagerUtil.get(this.drmUrl)
|
||||
let renewalServerUrl: string = licenseInfoMap.has('RenewalServerUrl') ? (licenseInfoMap.get('RenewalServerUrl') as string) : ''
|
||||
Logger.info(this.TAG, `RenewalServerUrl: ${renewalServerUrl}`)
|
||||
|
||||
await this.getLicense(this.drmInfoArr, this.drmUrl, renewalServerUrl, this.licenseType)
|
||||
// 离线renew需要删除旧id
|
||||
if (this.licenseType === 0 && oldKeyId) {
|
||||
let offlineLicenseId = new Uint8Array(TypeConversion.stringToByte(oldKeyId))
|
||||
this.mediaKeySystem.clearOfflineMediaKeys(offlineLicenseId)
|
||||
Logger.info(this.TAG, 'Renew - delete old offline license success.')
|
||||
}
|
||||
} else {
|
||||
Logger.error(this.TAG, 'renewAllowed: ' + renewAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,16 +310,42 @@ export default class DrmController {
|
||||
* @param svp 是否需要安全视频通路
|
||||
*/
|
||||
bindInstance(avPlayer: media.AVPlayer) {
|
||||
avPlayer.setDecryptConfig(this.mediaKeySession, this.svp)
|
||||
avPlayer.setDecryptionConfig(this.mediaKeySession, this.svp)
|
||||
Logger.info(this.TAG, 'DecryptConfig has been set.')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取license信息
|
||||
* @returns
|
||||
*/
|
||||
getLicenseInfo(): string {
|
||||
const mediaKeyStatus: drm.MediaKeyStatus[] = this.mediaKeySession.checkMediaKeyStatus()
|
||||
const licenseInfo: string = JSON.stringify(mediaKeyStatus)
|
||||
let licenseInfoText: string = ''
|
||||
|
||||
if (licenseInfo) {
|
||||
Logger.info(this.TAG, `licenseInfo: ${licenseInfo}`)
|
||||
const licenseInfoArr: drm.MediaKeyStatus[] = JSON.parse(licenseInfo)
|
||||
|
||||
licenseInfoText = 'License Info \n \n'
|
||||
licenseInfoArr.forEach((item: drm.MediaKeyStatus) => {
|
||||
licenseInfoText = licenseInfoText + item.name + ': ' + item.value + '\n'
|
||||
})
|
||||
|
||||
let licenseType: string = (AppStorage.get('licenseType') as number) === 0 ? 'offline' : 'online'
|
||||
licenseInfoText = licenseInfoText + 'LicenseType: ' + licenseType + '\n'
|
||||
}
|
||||
|
||||
this.licenseInfo = licenseInfo
|
||||
return licenseInfoText
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
*/
|
||||
releaseDrm() {
|
||||
if (!this.mediaKeySession) {
|
||||
Logger.error(this.TAG, 'releaseDrm: this.mediaKeySession is undefined!')
|
||||
Logger.error(this.TAG, 'this.mediaKeySession is undefined!')
|
||||
return
|
||||
}
|
||||
try {
|
||||
@ -276,4 +357,12 @@ export default class DrmController {
|
||||
Logger.error(this.TAG, `Failed to destroy Drm mediaKey. Error: [${err}]`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放avplayer播放资源
|
||||
* @param func
|
||||
*/
|
||||
getReleaseCallback(func: () => void) {
|
||||
this.releaseCallback = func
|
||||
}
|
||||
}
|
@ -22,27 +22,36 @@ export default class HttpUtil {
|
||||
static async getDrmResponse(url: string, _extraData: string): Promise<string> {
|
||||
let responseData: string = 'defaultStr'
|
||||
let httpRequest = http.createHttp()
|
||||
let response = await httpRequest.request(url, {
|
||||
method: http.RequestMethod.POST,
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
},
|
||||
extraData: _extraData,
|
||||
expectDataType: http.HttpDataType.STRING,
|
||||
})
|
||||
|
||||
if (response?.responseCode == 200) {
|
||||
if (typeof response.result == 'string') {
|
||||
responseData = response.result
|
||||
Logger.info(TAG, 'responseData Length:' + responseData.length)
|
||||
try {
|
||||
let response: http.HttpResponse = await httpRequest.request(url, {
|
||||
method: http.RequestMethod.POST,
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
},
|
||||
extraData: _extraData,
|
||||
expectDataType: http.HttpDataType.STRING,
|
||||
connectTimeout: 2000,
|
||||
readTimeout: 2000,
|
||||
})
|
||||
|
||||
if (response?.responseCode == 200) {
|
||||
if (typeof response.result == 'string') {
|
||||
responseData = response.result
|
||||
Logger.info(TAG, 'responseData Length:' + responseData.length)
|
||||
} else {
|
||||
Logger.error(TAG, 'error:' + typeof response.result)
|
||||
}
|
||||
httpRequest.destroy()
|
||||
} else {
|
||||
Logger.error(TAG, 'error:' + typeof response.result)
|
||||
Logger.error(TAG, 'error: responseCode' + response.responseCode)
|
||||
}
|
||||
httpRequest.destroy()
|
||||
} else {
|
||||
Logger.error(TAG, 'error: responseCode' + response.responseCode)
|
||||
|
||||
} catch (e) {
|
||||
Logger.error(TAG, 'getDrmResponse error: [' + JSON.stringify(e) + ']')
|
||||
}
|
||||
|
||||
return responseData
|
||||
}
|
||||
}
|
@ -93,7 +93,6 @@ export default struct OnlineListItemView {
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
|
||||
Row({ space: 10 }) {
|
||||
Image($r('app.media.download')).height(25)
|
||||
Image(this.isOpenOfflineLicenseArr[index] ? $r('app.media.key_blue') : $r('app.media.key'))
|
||||
.height(25)
|
||||
.onClick(() => {
|
||||
@ -122,9 +121,9 @@ export default struct OnlineListItemView {
|
||||
}
|
||||
})
|
||||
if (this.isOpenOfflineLicenseArr[index]) {
|
||||
AppStorage.setOrCreate('licenseType', 1);
|
||||
} else {
|
||||
AppStorage.setOrCreate('licenseType', 0);
|
||||
} else {
|
||||
AppStorage.setOrCreate('licenseType', 1);
|
||||
}
|
||||
})
|
||||
}.width('100%')
|
||||
|
@ -14,6 +14,7 @@
|
||||
*/
|
||||
|
||||
import router from '@ohos.router'
|
||||
import drm from '@ohos.multimedia.drm'
|
||||
import { AVPlayer } from '../../model/AVPlayer'
|
||||
import { getTimeString } from '../../model/TimeTools'
|
||||
import { samplesData } from '../../model/JsonListData'
|
||||
@ -38,6 +39,7 @@ export default struct OnlinePlayerView {
|
||||
@State isDeleteShow: boolean = false
|
||||
@State bitRateList: number[] = []
|
||||
@State errorText: string = ''
|
||||
@State licenseInfoText: string = ''
|
||||
private onlineVideoData: samplesData = {} as samplesData
|
||||
private mXComponentController: XComponentController = {} as XComponentController
|
||||
|
||||
@ -56,6 +58,10 @@ export default struct OnlinePlayerView {
|
||||
this.errorText = error
|
||||
})
|
||||
|
||||
this.myMedia.setLicenseInfoCallback((licenseInfo) => {
|
||||
this.licenseInfoText = licenseInfo
|
||||
})
|
||||
|
||||
setTimeout(async () => {
|
||||
let keyId: string = await KVManagerUtil.get(this.onlineVideoData.uri)
|
||||
this.isDeleteShow = keyId ? true : false
|
||||
@ -113,202 +119,215 @@ export default struct OnlinePlayerView {
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Stack({ alignContent: Alignment.Top }) {
|
||||
// 底层播放区域
|
||||
if (this.isXComponent) {
|
||||
Column() {
|
||||
Stack({ alignContent: Alignment.Top }) {
|
||||
// 底层播放区域
|
||||
if (this.isXComponent) {
|
||||
Column() {
|
||||
Column().layoutWeight(1)
|
||||
|
||||
XComponent({
|
||||
id: '',
|
||||
type: 'surface',
|
||||
controller: this.mXComponentController
|
||||
})
|
||||
.height(this.isLandscape ? '100%' : '55%')
|
||||
.aspectRatio(1920 / 1080)
|
||||
.onLoad(() => {
|
||||
this.surfaceId = Number(this.mXComponentController.getXComponentSurfaceId())
|
||||
this.isPlaying = true
|
||||
const uri: string = this.onlineVideoData.uri
|
||||
const licenseUrl: string = this.onlineVideoData.drm_license_url
|
||||
const title: string = this.onlineVideoData.name
|
||||
Logger.info(TAG, `drmUrl: ${uri}`)
|
||||
Logger.info(TAG, `drmLicenseUrl: ${licenseUrl}`)
|
||||
this.myMedia.drmInit(this.surfaceId.toString(), title, uri, licenseUrl)
|
||||
})
|
||||
Column().layoutWeight(2)
|
||||
}.zIndex(0)
|
||||
}
|
||||
|
||||
// 上层操作界面
|
||||
Column() {
|
||||
Column().layoutWeight(1)
|
||||
// 标题栏区域
|
||||
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceAround }) {
|
||||
Image($r('app.media.icon_back'))
|
||||
.width(24)
|
||||
.height(24)
|
||||
.margin(15)
|
||||
.onClick(() => {
|
||||
if (this.isLandscape) {
|
||||
this.isLandscape = false
|
||||
} else {
|
||||
router.back()
|
||||
}
|
||||
})
|
||||
|
||||
XComponent({
|
||||
id: '',
|
||||
type: 'surface',
|
||||
controller: this.mXComponentController
|
||||
})
|
||||
.height(this.isLandscape ? '100%' : '55%')
|
||||
.aspectRatio(1920 / 1080)
|
||||
.onLoad(() => {
|
||||
this.surfaceId = Number(this.mXComponentController.getXComponentSurfaceId())
|
||||
this.isPlaying = true
|
||||
const uri: string = this.onlineVideoData.uri
|
||||
const licenseUrl: string = this.onlineVideoData.drm_license_url
|
||||
const title: string = this.onlineVideoData.name
|
||||
Logger.info(TAG, `drmUrl: ${uri}`)
|
||||
Logger.info(TAG, `drmLicenseUrl: ${licenseUrl}`)
|
||||
this.myMedia.drmInit(this.surfaceId.toString(), title, uri, licenseUrl)
|
||||
})
|
||||
Column().layoutWeight(2)
|
||||
}.zIndex(0)
|
||||
}
|
||||
Text(this.myMedia.fileName || $r('app.string.phoneMain_title'))
|
||||
.fontColor('rgba(255,255,255,0.90)')
|
||||
.fontSize(18)
|
||||
.fontFamily('HarmonyHeiTi-Medium')
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.MARQUEE })
|
||||
.width('50%')
|
||||
|
||||
// 上层操作界面
|
||||
Column() {
|
||||
// 标题栏区域
|
||||
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceAround }) {
|
||||
Image($r('app.media.icon_back'))
|
||||
.width(24)
|
||||
.height(24)
|
||||
.margin(15)
|
||||
.onClick(() => {
|
||||
if (this.isLandscape) {
|
||||
this.isLandscape = false
|
||||
} else {
|
||||
router.back()
|
||||
}
|
||||
})
|
||||
Blank()
|
||||
|
||||
Text(this.myMedia.fileName || $r('app.string.phoneMain_title'))
|
||||
.fontColor('rgba(255,255,255,0.90)')
|
||||
.fontSize(18)
|
||||
.fontFamily('HarmonyHeiTi-Medium')
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.MARQUEE })
|
||||
.width('50%')
|
||||
|
||||
Blank()
|
||||
|
||||
Text($r('app.string.onlinePlayerView_bitrate'))
|
||||
.fontColor('rgba(255,255,255,0.90)')
|
||||
.fontSize(18)
|
||||
.fontFamily('HarmonyHeiTi-Medium')
|
||||
.margin(15)
|
||||
.onClick(() => {
|
||||
this.isBitrateShow = !this.isBitrateShow
|
||||
})
|
||||
.bindPopup(this.isBitrateShow, {
|
||||
builder: this.bitratePopup, // 气泡的内容
|
||||
placement: Placement.Bottom, // 气泡的弹出位置
|
||||
popupColor: 'rgba(0,0,0,0.6)' // 气泡的背景色
|
||||
})
|
||||
|
||||
Text($r('app.string.onlinePlayerView_speed'))
|
||||
.fontColor('rgba(255,255,255,0.90)')
|
||||
.fontSize(18)
|
||||
.fontFamily('HarmonyHeiTi-Medium')
|
||||
.margin(15)
|
||||
.onClick(() => {
|
||||
this.isSpeedShow = !this.isSpeedShow
|
||||
})
|
||||
.bindPopup(this.isSpeedShow, {
|
||||
builder: this.speedPopup, // 气泡的内容
|
||||
placement: Placement.Bottom, // 气泡的弹出位置
|
||||
popupColor: 'rgba(0,0,0,0.6)' // 气泡的背景色
|
||||
})
|
||||
}.layoutWeight(1)
|
||||
|
||||
// 提示
|
||||
Column() {
|
||||
// 加载中UI
|
||||
LoadingProgress()
|
||||
.color(Color.White)
|
||||
.opacity(0.6)
|
||||
.width(40)
|
||||
.height(40)
|
||||
.enableLoading(this.myMedia.isLoading ? true : false)
|
||||
|
||||
if (this.errorText) {
|
||||
Text(this.errorText)
|
||||
Text($r('app.string.onlinePlayerView_bitrate'))
|
||||
.fontColor('rgba(255,255,255,0.90)')
|
||||
.fontSize(18)
|
||||
.fontFamily('HarmonyHeiTi-Medium')
|
||||
.margin(15)
|
||||
}
|
||||
}
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.height('55%')
|
||||
.onClick(() => {
|
||||
this.isBitrateShow = !this.isBitrateShow
|
||||
})
|
||||
.bindPopup(this.isBitrateShow, {
|
||||
builder: this.bitratePopup, // 气泡的内容
|
||||
placement: Placement.Bottom, // 气泡的弹出位置
|
||||
popupColor: 'rgba(0,0,0,0.6)' // 气泡的背景色
|
||||
})
|
||||
|
||||
// 进度条区域
|
||||
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceEvenly }) {
|
||||
Image(this.isPlaying ? $r('app.media.icon_play') : $r('app.media.icon_pause'))
|
||||
.width(24)
|
||||
.height(24)
|
||||
.onClick(() => {
|
||||
this.isPlaying = !this.isPlaying
|
||||
})
|
||||
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
|
||||
Text(getTimeString(this.currentTime) || '00:00')
|
||||
.fontSize(12)
|
||||
.fontColor('#FFFFFF')
|
||||
.fontWeight(500)
|
||||
Text($r('app.string.onlinePlayerView_speed'))
|
||||
.fontColor('rgba(255,255,255,0.90)')
|
||||
.fontSize(18)
|
||||
.fontFamily('HarmonyHeiTi-Medium')
|
||||
.margin(15)
|
||||
.onClick(() => {
|
||||
this.isSpeedShow = !this.isSpeedShow
|
||||
})
|
||||
.bindPopup(this.isSpeedShow, {
|
||||
builder: this.speedPopup, // 气泡的内容
|
||||
placement: Placement.Bottom, // 气泡的弹出位置
|
||||
popupColor: 'rgba(0,0,0,0.6)' // 气泡的背景色
|
||||
})
|
||||
}.layoutWeight(1)
|
||||
|
||||
// 提示
|
||||
Column() {
|
||||
// 加载中UI
|
||||
LoadingProgress()
|
||||
.color(Color.White)
|
||||
.opacity(0.6)
|
||||
.fontFamily('HarmonyHeiTi')
|
||||
Slider({
|
||||
value: Math.floor((this.currentTime / 1000)),
|
||||
max: Math.floor(this.myMedia.totalDuration / 1000)
|
||||
},)
|
||||
.layoutWeight(1)
|
||||
.selectedColor(Color.White)
|
||||
.trackColor(Color.Gray)
|
||||
.onChange((value: number, mode: SliderChangeMode) => {
|
||||
let changeTime = Math.floor(value) * 1000
|
||||
if (mode === SliderChangeMode.Begin) {
|
||||
this.isProgressMoving = true
|
||||
} else if (mode === SliderChangeMode.Moving) {
|
||||
this.isProgressMoving = true
|
||||
this.currentTime = changeTime
|
||||
} else if (mode === SliderChangeMode.End) {
|
||||
this.isProgressMoving = false
|
||||
this.myMedia.seek(changeTime)
|
||||
.width(40)
|
||||
.height(40)
|
||||
.enableLoading(this.myMedia.isLoading ? true : false)
|
||||
|
||||
if (this.errorText) {
|
||||
Text(this.errorText)
|
||||
.fontColor('rgba(255,255,255,0.90)')
|
||||
.fontSize(18)
|
||||
.fontFamily('HarmonyHeiTi-Medium')
|
||||
.margin(15)
|
||||
}
|
||||
}
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.height('55%')
|
||||
|
||||
// 进度条区域
|
||||
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceEvenly }) {
|
||||
Image(this.isPlaying ? $r('app.media.icon_play') : $r('app.media.icon_pause'))
|
||||
.width(24)
|
||||
.height(24)
|
||||
.onClick(() => {
|
||||
this.isPlaying = !this.isPlaying
|
||||
})
|
||||
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
|
||||
Text(getTimeString(this.currentTime) || '00:00')
|
||||
.fontSize(12)
|
||||
.fontColor('#FFFFFF')
|
||||
.fontWeight(500)
|
||||
.opacity(0.6)
|
||||
.fontFamily('HarmonyHeiTi')
|
||||
Slider({
|
||||
value: Math.floor((this.currentTime / 1000)),
|
||||
max: Math.floor(this.myMedia.totalDuration / 1000)
|
||||
},)
|
||||
.layoutWeight(1)
|
||||
.selectedColor(Color.White)
|
||||
.trackColor(Color.Gray)
|
||||
.onChange((value: number, mode: SliderChangeMode) => {
|
||||
let changeTime = Math.floor(value) * 1000
|
||||
if (mode === SliderChangeMode.Begin) {
|
||||
this.isProgressMoving = true
|
||||
} else if (mode === SliderChangeMode.Moving) {
|
||||
this.isProgressMoving = true
|
||||
this.currentTime = changeTime
|
||||
} else if (mode === SliderChangeMode.End) {
|
||||
this.isProgressMoving = false
|
||||
this.myMedia.seek(changeTime)
|
||||
}
|
||||
})
|
||||
Text(this.myMedia ? getTimeString(this.myMedia.totalDuration) : '00:00')
|
||||
.fontSize(12)
|
||||
.fontColor('#FFFFFF')
|
||||
.fontWeight(500)
|
||||
.opacity(0.6)
|
||||
.fontFamily('HarmonyHeiTi')
|
||||
}.width('80%')
|
||||
|
||||
Image($r('app.media.icon_large'))
|
||||
.width(24)
|
||||
.height(24)
|
||||
.opacity(this.isLandscape ? 1 : 0.4)
|
||||
.onClick(() => {
|
||||
this.isLandscape = !this.isLandscape
|
||||
})
|
||||
}
|
||||
.layoutWeight(1)
|
||||
|
||||
// 控制栏
|
||||
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceAround }) {
|
||||
Image($r("app.media.icon_seekStart"))
|
||||
.width(24)
|
||||
.height(24)
|
||||
.onClick(() => {
|
||||
this.myMedia.seek(0)
|
||||
})
|
||||
|
||||
Image($r('app.media.icon_cycle'))
|
||||
.width(24)
|
||||
.height(24)
|
||||
.opacity(this.isLoop ? 1 : 0.4)
|
||||
.onClick(() => {
|
||||
this.isLoop = !this.isLoop
|
||||
})
|
||||
|
||||
Image($r("app.media.icon_seekEnd"))
|
||||
.width(24)
|
||||
.height(24)
|
||||
.onClick(() => {
|
||||
this.myMedia.seek(this.myMedia.totalDuration)
|
||||
})
|
||||
|
||||
Image($r("app.media.delete"))
|
||||
.width(24)
|
||||
.height(24)
|
||||
.visibility(this.isDeleteShow ? Visibility.Visible : Visibility.Hidden)
|
||||
.onClick(async () => {
|
||||
if (this.isDeleteShow) {
|
||||
this.isDeleteShow = false
|
||||
this.myMedia.deleteOfflineLicense(this.onlineVideoData.uri)
|
||||
}
|
||||
})
|
||||
Text(this.myMedia ? getTimeString(this.myMedia.totalDuration) : '00:00')
|
||||
.fontSize(12)
|
||||
.fontColor('#FFFFFF')
|
||||
.fontWeight(500)
|
||||
.opacity(0.6)
|
||||
.fontFamily('HarmonyHeiTi')
|
||||
}.width('80%')
|
||||
|
||||
Image($r('app.media.icon_large'))
|
||||
.width(24)
|
||||
.height(24)
|
||||
.opacity(this.isLandscape ? 1 : 0.4)
|
||||
.onClick(() => {
|
||||
this.isLandscape = !this.isLandscape
|
||||
})
|
||||
}
|
||||
.layoutWeight(1)
|
||||
}
|
||||
.layoutWeight(1)
|
||||
|
||||
// 控制栏
|
||||
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceAround }) {
|
||||
Image($r("app.media.icon_seekStart"))
|
||||
.width(24)
|
||||
.height(24)
|
||||
.onClick(() => {
|
||||
this.myMedia.seek(0)
|
||||
})
|
||||
|
||||
Image($r('app.media.icon_cycle'))
|
||||
.width(24)
|
||||
.height(24)
|
||||
.opacity(this.isLoop ? 1 : 0.4)
|
||||
.onClick(() => {
|
||||
this.isLoop = !this.isLoop
|
||||
})
|
||||
|
||||
Image($r("app.media.icon_seekEnd"))
|
||||
.width(24)
|
||||
.height(24)
|
||||
.onClick(() => {
|
||||
this.myMedia.seek(this.myMedia.totalDuration)
|
||||
})
|
||||
|
||||
Image($r("app.media.delete"))
|
||||
.width(24)
|
||||
.height(24)
|
||||
.visibility(this.isDeleteShow ? Visibility.Visible : Visibility.Hidden)
|
||||
.onClick(async () => {
|
||||
if (this.isDeleteShow) {
|
||||
this.isDeleteShow = false
|
||||
this.myMedia.deleteOfflineLicense(this.onlineVideoData.uri)
|
||||
}
|
||||
})
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.zIndex(1)
|
||||
}
|
||||
.zIndex(1)
|
||||
}
|
||||
.height(this.isLandscape ? '100%' : '50%')
|
||||
.backgroundColor(Color.Black)
|
||||
|
||||
|
||||
Column() {
|
||||
Text(this.licenseInfoText)
|
||||
.fontColor(Color.White)
|
||||
}
|
||||
.width('100%')
|
||||
.height(this.isLandscape ? '0%' : '50%')
|
||||
.padding(10)
|
||||
}
|
||||
.height(this.isLandscape ? '100%' : '50%')
|
||||
.backgroundColor(Color.Black)
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
}
|
||||
}
|
@ -50,10 +50,24 @@
|
||||
],
|
||||
"requestPermissions": [
|
||||
{
|
||||
"name": "ohos.permission.WRITE_MEDIA"
|
||||
"name": "ohos.permission.WRITE_MEDIA",
|
||||
"reason": "$string:reason",
|
||||
"usedScene": {
|
||||
"abilities":[
|
||||
"MainAbility"
|
||||
],
|
||||
"when": "inuse",
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ohos.permission.READ_MEDIA"
|
||||
"name": "ohos.permission.READ_MEDIA",
|
||||
"reason": "$string:reason",
|
||||
"usedScene": {
|
||||
"abilities":[
|
||||
"MainAbility"
|
||||
],
|
||||
"when": "inuse",
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ohos.permission.INTERNET"
|
||||
|
@ -231,6 +231,10 @@
|
||||
{
|
||||
"name": "onlinePlayerView_speed",
|
||||
"value": "倍速"
|
||||
},
|
||||
{
|
||||
"name": "reason",
|
||||
"value": "get permission"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,470 +1,42 @@
|
||||
[
|
||||
{
|
||||
"name": "DTA 264 HLS",
|
||||
"name": "WisePlay加密测试影片",
|
||||
"samples": [
|
||||
{
|
||||
"name": "ChinaDRMLab-264-30-480P-2000K-TS-SM4C",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"name": "sm4c ChinaDRMLab_Promo_210525-264-30-480P-2000K",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/20240117/h264-sm4c-hls/ChinaDRMLab_Promo_210525-264-30-480P-2000K/sm4c.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "ChinaDRMLab-264-30-720P-5000K-TS-SM4C",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-720P-5000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "ChinaDRMLab-264-30-1080P-10000K-Clear HLS",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-1080P-10000K-Clear/index.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "ChinaDRMLab-264-30-1080P-10000K-TS-CBC1",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-1080P-10000K-TS-CBC1/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "ChinaDRMLab-264-30-1080P-10000K-TS-SM4C",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-1080P-10000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "ChinaDRMLab-264-30-1080P-10000K-TS-SM4S",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-1080P-10000K-TS-SM4S/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "错误license地址",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-1080P-10000K-TS-SM4S/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8081/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "H265 HLS clear",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/LaunchEvent/DASH_MP4_H265_clear/hls.m3u8"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "wiseplay policy test (OBS桶片源+DTAlicense)",
|
||||
"samples": [
|
||||
{
|
||||
"name": "test-nocontent security level 1 no HDCP",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policy111101ibL"
|
||||
},
|
||||
{
|
||||
"name": "test-ChinaDRMLab-264-30-480P-2000K-TS-SM4C",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "security level 1 no HDCP",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest01ibL"
|
||||
},
|
||||
{
|
||||
"name": "security level 2 no HDCP",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest02ibL"
|
||||
},
|
||||
{
|
||||
"name": "security level 3 no HDCP",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest03ibL"
|
||||
},
|
||||
{
|
||||
"name": "security level 0",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest04ibL"
|
||||
},
|
||||
{
|
||||
"name": "without security level",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest05ibL"
|
||||
},
|
||||
{
|
||||
"name": "NONPERSISTENT",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest06ibL"
|
||||
},
|
||||
{
|
||||
"name": "without PERSISTENT",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest13ibL"
|
||||
},
|
||||
{
|
||||
"name": "wrong PERSISTENT",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest06ib1"
|
||||
},
|
||||
{
|
||||
"name": "playbackduration 70",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest07ibL"
|
||||
},
|
||||
{
|
||||
"name": "rentalduration 80",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest08ibL"
|
||||
},
|
||||
{
|
||||
"name": "playbackduration 100 rentalduration 70",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytestpribL"
|
||||
},
|
||||
{
|
||||
"name": "playbackduration 70 rentalduration 100",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytestrpibL"
|
||||
},
|
||||
{
|
||||
"name": "playbackduration 700000 rentalduration 800000",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest25ibL"
|
||||
},
|
||||
{
|
||||
"name": "playbackduration 4294967296",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest07ib1"
|
||||
},
|
||||
{
|
||||
"name": "playbackduration -1",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest07ib2"
|
||||
},
|
||||
{
|
||||
"name": "playbackduration 0",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest07ib3"
|
||||
},
|
||||
{
|
||||
"name": "playbackduration 4294967295",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest07ib4"
|
||||
},
|
||||
{
|
||||
"name": "playbackduration 4294967294",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest07ib5"
|
||||
},
|
||||
{
|
||||
"name": "playbackduration 1",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest07ib6"
|
||||
},
|
||||
{
|
||||
"name": "rentalDuration 4294967296",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest08ib1"
|
||||
},
|
||||
{
|
||||
"name": "rentalDuration -1",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest08ib2"
|
||||
},
|
||||
{
|
||||
"name": "rentalDuration 0",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest08ib3"
|
||||
},
|
||||
{
|
||||
"name": "rentalDuration 4294967295",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest08ib4"
|
||||
},
|
||||
{
|
||||
"name": "rentalDuration 4294967294",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest08ib5"
|
||||
},
|
||||
{
|
||||
"name": "rentalDuration 1",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest08ib6"
|
||||
},
|
||||
{
|
||||
"name": "start endtime 0",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest091bL"
|
||||
},
|
||||
{
|
||||
"name": "start 2003-11-22 09:33:25 endtime 0",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest092bL"
|
||||
},
|
||||
{
|
||||
"name": "start 0 endtime 2030-11-22 09:33:25",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest093bL"
|
||||
},
|
||||
{
|
||||
"name": "starttime 20200720 endtime 20300122",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest10ibL"
|
||||
},
|
||||
{
|
||||
"name": "未开始",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest11ibL"
|
||||
},
|
||||
{
|
||||
"name": "已失效",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest12ibL"
|
||||
},
|
||||
{
|
||||
"name": "canrenew false",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest14ibL"
|
||||
},
|
||||
{
|
||||
"name": "without canrenew",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest17ibL"
|
||||
},
|
||||
{
|
||||
"name": "no renewdelayseconds",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest151bL"
|
||||
},
|
||||
{
|
||||
"name": "renew every 20s",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest15ibL"
|
||||
},
|
||||
{
|
||||
"name": "retry every 15s",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest16ibL"
|
||||
},
|
||||
{
|
||||
"name": "renew every 20s 15s",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest15ib1"
|
||||
},
|
||||
{
|
||||
"name": "renew every 15s wrong key",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest31ibL"
|
||||
},
|
||||
{
|
||||
"name": "no renewurl",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest26ibL"
|
||||
},
|
||||
{
|
||||
"name": "endtime is earlier than starttime没配置,后续和产线的一起测",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest24ibL"
|
||||
},
|
||||
{
|
||||
"name": "Expired during playback需要测试时配置",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest25ibL"
|
||||
},
|
||||
{
|
||||
"name": "Audio only暂无调通片源",
|
||||
"uri": "http://10.33.105.159:8080/rem/yqd/yinpin/yinpin_dash.mpd",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=audioonlyefmABc"
|
||||
},
|
||||
{
|
||||
"name": "多分辨率SL SD1 HD2 UHD3",
|
||||
"uri": "http://10.33.105.159:8080/rem/LaunchEvent/DASH_MP4_H265_SD&HD/dash.mpd",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest23ibL"
|
||||
},
|
||||
{
|
||||
"name": "多分辨率SL SD1 HD2 UHD3",
|
||||
"uri": "http://10.33.105.159:8080/rem/LaunchEvent/DASH_MP4_H265_SD&HD/dash.mpd",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest233bL"
|
||||
},
|
||||
{
|
||||
"name": "多分辨率HDCP SD1 HD2 UHD3 SL2",
|
||||
"uri": "http://10.33.105.159:8080/rem/LaunchEvent/DASH_MP4_H265_SD&HD/dash.mpd",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policytest22ibL"
|
||||
},
|
||||
{
|
||||
"name": "no HDCP",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policyhdcpnoibL"
|
||||
},
|
||||
{
|
||||
"name": "HDCP 0 HW EH",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policyhdcp00ibL"
|
||||
},
|
||||
{
|
||||
"name": "HDCP 1 HW EH",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policyhdcp01ibL"
|
||||
},
|
||||
{
|
||||
"name": "HDCP 2 HW EH",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policyhdcp02ibL"
|
||||
},
|
||||
{
|
||||
"name": "HDCP 3 HW EH",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policyhdcp03ibL"
|
||||
},
|
||||
{
|
||||
"name": "HDCP 0 SW",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policyhdcp00sbL"
|
||||
},
|
||||
{
|
||||
"name": "HDCP 1 SW",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policyhdcp01sbL"
|
||||
},
|
||||
{
|
||||
"name": "HDCP 2 SW",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policyhdcp02sbL"
|
||||
},
|
||||
{
|
||||
"name": "HDCP 3 SW",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense?contentId=policyhdcp03sbL"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DTA HLS+TS 264 new",
|
||||
"samples": [
|
||||
{
|
||||
"name": "sm4c ChinaDRMLab_Promo_210525-264-30-1080P-10000K",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/SM_xin/h264-sm4c-hls/ChinaDRMLab_Promo_210525-264-30-1080P-10000K/sm4c.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
"drm_license_url": "http://license.dev.trustdta.com:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "sm4c ChinaDRMLab_Promo_210525-264-30-720P-5000K",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/SM_xin/h264-sm4c-hls/ChinaDRMLab_Promo_210525-264-30-720P-5000K/sm4c.m3u8",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/20240117/h264-sm4c-hls/ChinaDRMLab_Promo_210525-264-30-720P-5000K/sm4c.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
"drm_license_url": "http://license.dev.trustdta.com:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "sm4c ChinaDRMLab_Promo_210525-264-30-480P-2000K",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/SM_xin/h264-sm4c-hls/ChinaDRMLab_Promo_210525-264-30-480P-2000K/sm4c.m3u8",
|
||||
"name": "sm4c ChinaDRMLab_Promo_210525-264-30-1080P-10000K",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/20240117/h264-sm4c-hls/ChinaDRMLab_Promo_210525-264-30-1080P-10000K/sm4c.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
"drm_license_url": "http://license.dev.trustdta.com:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "sm4r ChinaDRMLab_Promo_210525-264-30-1080P-10000K",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/SM_xin/h264-sm4r-hls/ChinaDRMLab_Promo_210525-264-30-1080P-10000K/sm4r.m3u8",
|
||||
"name": "ChinaDRMLab-264-30-1080P-10000K-TS-CBC1 可离线",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-1080P-10000K-TS-CBC1/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
"drm_license_url": "http://license.dev.trustdta.com:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "sm4r ChinaDRMLab_Promo_210525-264-30-720P-5000K",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/SM_xin/h264-sm4r-hls/ChinaDRMLab_Promo_210525-264-30-720P-5000K/sm4r.m3u8",
|
||||
"name": "playbackduration 70s 后过期",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
"drm_license_url": "http://license.dev.trustdta.com:8080/drmproxy/v3/getLicense?contentId=policytest07ibL"
|
||||
},
|
||||
{
|
||||
"name": "sm4r ChinaDRMLab_Promo_210525-264-30-480P-2000K",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/SM_xin/h264-sm4r-hls/ChinaDRMLab_Promo_210525-264-30-480P-2000K/sm4r.m3u8",
|
||||
"name": "每20s进行一次renew ",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/ChinaDRMLab-264-30-480P-2000K-TS-SM4C/playlist.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "sm4s ChinaDRMLab_Promo_210525-264-30-1080P-10000K",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/SM_xin/h264-sm4s-hls/ChinaDRMLab_Promo_210525-264-30-1080P-10000K/sm4s.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "sm4s ChinaDRMLab_Promo_210525-264-30-720P-5000K",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/SM_xin/h264-sm4s-hls/ChinaDRMLab_Promo_210525-264-30-480P-2000K/sm4s.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "sm4s ChinaDRMLab_Promo_210525-264-30-480P-2000K",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/SM_xin/h264-sm4s-hls/ChinaDRMLab_Promo_210525-264-30-480P-2000K/sm4s.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "sm4t ChinaDRMLab_Promo_210525-264-30-1080P-10000K",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/SM_xin/h264-sm4t-hls/ChinaDRMLab_Promo_210525-264-30-1080P-10000K/sm4t.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "sm4t ChinaDRMLab_Promo_210525-264-30-720P-5000K",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/SM_xin/h264-sm4t-hls/ChinaDRMLab_Promo_210525-264-30-720P-5000K/sm4t.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
},
|
||||
{
|
||||
"name": "sm4t ChinaDRMLab_Promo_210525-264-30-480P-2000K",
|
||||
"uri": "https://poster-inland.hwcloudtest.cn/rem/DTA/SM_xin/h264-sm4t-hls/ChinaDRMLab_Promo_210525-264-30-480P-2000K/sm4t.m3u8",
|
||||
"drm_scheme": "chinadrm",
|
||||
"drm_license_url": "http://120.46.155.105:8080/drmproxy/v3/getLicense"
|
||||
"drm_license_url": "http://license.dev.trustdta.com:8080/drmproxy/v3/getLicense?contentId=policytest15ibL"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,6 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2022-2024 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.
|
||||
*/
|
||||
{
|
||||
"hvigorVersion": "4.0.2",
|
||||
"hvigorVersion": "4.2.0",
|
||||
"dependencies": {
|
||||
"@ohos/hvigor-ohos-plugin": "4.0.2"
|
||||
"@ohos/hvigor-ohos-plugin": "4.2.0"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 61 KiB |
11
code/BasicFeature/Media/AVSession/AvCastPickerForCall/.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
/node_modules
|
||||
/oh_modules
|
||||
/local.properties
|
||||
/.idea
|
||||
**/build
|
||||
/.hvigor
|
||||
.cxx
|
||||
/.clangd
|
||||
/.clang-format
|
||||
/.clang-tidy
|
||||
**/.test
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.avcastpickerforcall",
|
||||
"vendor": "example",
|
||||
"versionCode": 1000000,
|
||||
"versionName": "1.0.0",
|
||||
"icon": "$media:app_icon",
|
||||
"label": "$string:app_name"
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "app_name",
|
||||
"value": "AvCastPickerForCall"
|
||||
}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 2.0 KiB |
219
code/BasicFeature/Media/AVSession/AvCastPickerForCall/README.md
Normal file
@ -0,0 +1,219 @@
|
||||
# 通话设备切换
|
||||
|
||||
### 介绍
|
||||
|
||||
本示例主要展示了通话设备切换的相关功能,使用[@ohos.multimedia.avCastPicker](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-avsession-kit/ohos-multimedia-avcastpicker.md)和[@ohos.multimedia.avCastPickerParam](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-avsession-kit/js-apis-avCastPickerParam.md)等接口实现通话设备切换功能。
|
||||
|
||||
> 注意:
|
||||
> 通话设备切换按钮可显示默认样式或者自定义样式,默认样式由系统提供,自定义组件由应用提供。还可参考相关的[开发指南](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/media/avsession/using-switch-call-devices.md)。
|
||||
|
||||
### 效果预览
|
||||
|
||||
| 主页 | 默认样式页面 | 自定义样式页面 |
|
||||
|-------------------------------- | -------------------------------- | -------------------------------- |
|
||||
| ![Index](screenshots/device/Index.jpeg) | ![DefaultPicker](screenshots/device/DefaultPicker.jpeg) | ![CustomPicker](screenshots/device/CustomPicker.jpeg) |
|
||||
|
||||
### 使用说明
|
||||
|
||||
1. 点击进入主页,可以选择点击按钮默认样式组件和自定义样式组件,如果点击默认样式组件,跳转到显示默认样式的页面;点击自定义样式组件,跳转到显示自定义样式的页面;
|
||||
2. 进入页面后,有三个按钮,分别是start,stop和设备切换按钮,点击start按钮,可播放音频,点击stop,可暂停音频, 点击设备切换按钮,可选择当前输出设备并显示设备对应的样式。
|
||||
|
||||
### 工程目录
|
||||
|
||||
项目中关键的目录结构如下:
|
||||
|
||||
```
|
||||
entry/src/main/ets/
|
||||
|---pages
|
||||
|---|---Index.ets //初始界面实现
|
||||
|---|---DefaultPicker.ets //默认样式实现
|
||||
|---|---CustomPicker.ets //自定义样式实现
|
||||
|
||||
```
|
||||
|
||||
### 具体实现
|
||||
|
||||
#### 初始界面实现
|
||||
|
||||
初始界面相关的实现都封装在pages/Index.ets下,源码参考:[pages/Index.ets](./entry/src/main/ets/pages/Index.ets)。
|
||||
|
||||
* 界面显示两个按钮分别为默认样式组件和自定义样式组件,可分别跳转到系统默认样式实现界面和自定义样式实现界面,相关代码如下:
|
||||
|
||||
```ets
|
||||
Button() {
|
||||
// 按钮的内容
|
||||
}
|
||||
.onClick(async() => {
|
||||
await router.pushUrl({ url:'pages/DefaultPicker' });//系统默认样式实现
|
||||
})
|
||||
|
||||
Button() {
|
||||
// 按钮的内容
|
||||
}
|
||||
.onClick(async() => {
|
||||
await router.pushUrl({ url:'pages/CustomPicker' });//自定义样式实现
|
||||
})
|
||||
```
|
||||
#### 默认系统样式
|
||||
|
||||
默认系统样式的相关内容封装在pages/DefaultPicker.ets下,源码参考:[pages/DefaultPicker.ets](./entry/src/main/ets/pages/DefaultPicker.ets)。
|
||||
|
||||
* 在需要切换设备的通话界面创建AVCastPicker组件。
|
||||
|
||||
```ets
|
||||
import { AVCastPicker } from '@kit.AVSessionKit';
|
||||
|
||||
AVCastPicker({
|
||||
normalColor: this.avCastPickerColor,
|
||||
activeColor: this.avCastPickerColor,
|
||||
})
|
||||
.size({ width: 45, height: 45 })
|
||||
```
|
||||
|
||||
* 拉起组件前需创建voice_call类型的AVSession,AVSession在构造方法中支持不同的类型参数,由AVSessionType定义,voice_call表示通话类型。
|
||||
|
||||
```ets
|
||||
import { avSession } from '@kit.AVSessionKit';
|
||||
|
||||
init(){
|
||||
//...
|
||||
this.session = await avSession.createAVSession(this.appContext, 'voiptest', 'voice_call');
|
||||
}
|
||||
```
|
||||
|
||||
* 创建VOICE_COMMUNICATION类型的AudioRenderer,并播放,可模拟通话场景:
|
||||
|
||||
* 创建如下三个变量。
|
||||
|
||||
```ets
|
||||
private audioRendererInfo: audio.AudioRendererInfo = {
|
||||
usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION,
|
||||
rendererFlags: 0
|
||||
}
|
||||
private audioStreamInfo: audio.AudioStreamInfo = {
|
||||
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率
|
||||
channels: audio.AudioChannel.CHANNEL_2, // 通道
|
||||
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
|
||||
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
|
||||
}
|
||||
private audioRendererOption: audio.AudioRendererOptions = {
|
||||
streamInfo: this.audioStreamInfo,
|
||||
rendererInfo: this.audioRendererInfo
|
||||
}
|
||||
```
|
||||
|
||||
* 调用 startRenderer() 播放音频。
|
||||
|
||||
* 调用 stopRenderer() 暂停播放音频。
|
||||
|
||||
#### 自定义样式
|
||||
|
||||
自定义样式的相关内容封装在pages/CustomPicker.ets下,源码参考:[pages/CustomPicker.ets](./entry/src/main/ets/pages/CustomPicker.ets)。
|
||||
|
||||
* 在需要切换设备的通话界面创建AVCastPicker组件。
|
||||
|
||||
```ets
|
||||
|
||||
import { AVCastPicker } from '@kit.AVSessionKit';
|
||||
|
||||
@State pickerImage: ResourceStr = $r('app.media.ic_earpiece');
|
||||
|
||||
@Builder
|
||||
ImangeBuilder(): void {
|
||||
Image(this.pickerImage)
|
||||
.size({ width: '100%', height: '100%' })
|
||||
.backgroundColor('#00000000')
|
||||
.fillColor(Color.Black)
|
||||
}
|
||||
|
||||
Column() {
|
||||
Button() {
|
||||
AVCastPicker({
|
||||
normalColor: this.avCastPickerColor,
|
||||
activeColor: this.avCastPickerColor,
|
||||
customPicker: (): void => this.ImangeBuilder(),
|
||||
onStateChange: (state: AVCastPickerState) => {
|
||||
console.error(`change avcastpicker: ${state}`);
|
||||
}
|
||||
})
|
||||
.size({ width: 45, height: 45 })
|
||||
}
|
||||
.size({ width: 64, height: 64 })
|
||||
.type(ButtonType.Circle)
|
||||
.backgroundColor(Color.Orange)
|
||||
}
|
||||
.size({ width: '33%', height: 64 })
|
||||
|
||||
```
|
||||
|
||||
* 拉起组件前也需创建voice_call类型的AVSession。为了保证能够正常更新样式,需要监听当前设备切换,如果当前设备切换,将刷新显示样式
|
||||
|
||||
```ets
|
||||
|
||||
import { avSession } from '@kit.AVSessionKit';
|
||||
import { audio } from '@kit.AudioKit';
|
||||
|
||||
init(){
|
||||
this.observerDevices();
|
||||
this.session = await avSession.createAVSession(this.appContext, 'voiptest', 'voice_call');
|
||||
}
|
||||
|
||||
async observerDevices() {
|
||||
if(!this.audioRoutingManager) {
|
||||
return;
|
||||
}
|
||||
let desc: audio.AudioDeviceDescriptors = this.audioRoutingManager.getPreferredOutputDeviceForRendererInfoSync(this.audioRendererInfo);
|
||||
this.changePickerShow(desc);
|
||||
audioRoutingManager.on('preferOutputDeviceChangeForRendererInfo', this.audioRendererInfo, (desc: audio.AudioDeviceDescriptors) => {
|
||||
if(!this.audioRoutingManager) {
|
||||
return;
|
||||
}
|
||||
console.log(`device change to: ${desc[0].deviceType}`);
|
||||
let devices: audio.AudioDeviceDescriptors = this.audioRoutingManager.getPreferredOutputDeviceForRendererInfoSync(this.audioRendererInfo);
|
||||
this.changePickerShow(devices);
|
||||
});
|
||||
}
|
||||
|
||||
private changePickerShow(desc: audio.AudioDeviceDescriptors) {
|
||||
if (desc[0].deviceType === 2) {
|
||||
this.pickerImage = $r('app.media.ic_public_sound');
|
||||
} else if (desc[0].deviceType === 7) {
|
||||
this.pickerImage = $r('app.media.ic_bluetooth');
|
||||
} else {
|
||||
this.pickerImage = $r('app.media.ic_earpiece');
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
* 创建VOICE_COMMUNICATION类型的AudioRenderer,并播放,可模拟通话场景,具体实现如默认样式。
|
||||
|
||||
### 相关权限
|
||||
|
||||
不涉及
|
||||
|
||||
### 依赖
|
||||
|
||||
不涉及
|
||||
|
||||
### 约束与限制
|
||||
|
||||
1. 本示例仅支持标准系统上运行。
|
||||
|
||||
2. 本示例为Stage模型,支持API12版本SDK,SDK版本号(API Version 12 Release),镜像版本号(5.0 Release)。
|
||||
|
||||
3. 本示例需要使用DevEco Studio 版本号(5.0 Release)及以上版本才可编译运行。
|
||||
|
||||
4. 本示例手机设备支持,RK暂不支持。
|
||||
|
||||
### 下载
|
||||
|
||||
如需单独下载本工程,执行如下命令:
|
||||
|
||||
```
|
||||
git init
|
||||
git config core.sparsecheckout true
|
||||
echo code/BasicFeature/Media/AVSession/AvCastPickerForCall > .git/info/sparse-checkout
|
||||
git remote add origin https://gitee.com/openharmony/applications_app_samples.git
|
||||
git pull origin master
|
||||
```
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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": 12,
|
||||
"compatibleSdkVersion": 12,
|
||||
"runtimeOS": "OpenHarmony"
|
||||
}
|
||||
]
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"name": "entry",
|
||||
"srcPath": "./entry",
|
||||
"targets": [
|
||||
{
|
||||
"name": "default",
|
||||
"applyToProducts": [
|
||||
"default"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
6
code/BasicFeature/Media/AVSession/AvCastPickerForCall/entry/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/node_modules
|
||||
/oh_modules
|
||||
/.preview
|
||||
/build
|
||||
/.cxx
|
||||
/.test
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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",
|
||||
}
|
||||
]
|
||||
}
|
@ -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. */
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.
|
||||
*/
|
||||
|
||||
{
|
||||
"name": "entry",
|
||||
"version": "1.0.0",
|
||||
"description": "Please describe the basic information.",
|
||||
"main": "",
|
||||
"author": "",
|
||||
"license": "",
|
||||
"dependencies": {}
|
||||
}
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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';
|
||||
|
||||
export default class EntryAbility extends UIAbility {
|
||||
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
|
||||
}
|
||||
|
||||
onDestroy(): void {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
|
||||
}
|
||||
|
||||
onWindowStageCreate(windowStage: window.WindowStage): void {
|
||||
// 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) ?? '');
|
||||
});
|
||||
}
|
||||
|
||||
onWindowStageDestroy(): void {
|
||||
// Main window is destroyed, release UI related resources
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
|
||||
}
|
||||
|
||||
onForeground(): void {
|
||||
// Ability has brought to foreground
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
|
||||
}
|
||||
|
||||
onBackground(): void {
|
||||
// Ability has back to background
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
|
||||
}
|
||||
}
|
@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 { audio } from '@kit.AudioKit';
|
||||
import fs from '@ohos.file.fs';
|
||||
import { BusinessError } from '@ohos.base';
|
||||
import resourceManager from '@ohos.resourceManager';
|
||||
import common from '@ohos.app.ability.common';
|
||||
import { avSession, AVCastPicker, AVCastPickerState } from '@kit.AVSessionKit';
|
||||
|
||||
class Options {
|
||||
public offset: number = 0;
|
||||
public length: number = 0;
|
||||
}
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct customPicker {
|
||||
@State session: avSession.AVSession | undefined = undefined;
|
||||
@State avCastPickerColor:Color = Color.White;
|
||||
@State pickerImage: ResourceStr = $r('app.media.ic_earpiece');
|
||||
private appContext?: common.Context | undefined = undefined;
|
||||
private audioManager: audio.AudioManager | undefined = undefined;
|
||||
private audioRoutingManager: audio.AudioRoutingManager | undefined = undefined;
|
||||
private audioRenderer: audio.AudioRenderer | undefined = undefined;
|
||||
private audioSource = 'test1.wav';
|
||||
private fileDescriptor?: resourceManager.RawFileDescriptor | undefined = undefined;
|
||||
private audioRendererInfo: audio.AudioRendererInfo = {
|
||||
usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION,
|
||||
rendererFlags: 0,
|
||||
}
|
||||
private audioStreamInfo: audio.AudioStreamInfo = {
|
||||
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率
|
||||
channels: audio.AudioChannel.CHANNEL_2, // 通道
|
||||
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
|
||||
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
|
||||
}
|
||||
private audioRendererOption: audio.AudioRendererOptions = {
|
||||
streamInfo: this.audioStreamInfo,
|
||||
rendererInfo: this.audioRendererInfo
|
||||
}
|
||||
|
||||
async aboutToAppear() {
|
||||
console.log('about to appear');
|
||||
await this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this.appContext) {
|
||||
this.appContext = getContext();
|
||||
}
|
||||
this.audioManager = audio.getAudioManager();
|
||||
if (!this.audioManager) {
|
||||
console.error('get audioManager faild');
|
||||
return;
|
||||
}
|
||||
this.audioRoutingManager = this.audioManager.getRoutingManager();
|
||||
this.observerDevices();
|
||||
this.session = await avSession.createAVSession(this.appContext, 'voiptest', 'voice_call');
|
||||
}
|
||||
|
||||
async observerDevices() {
|
||||
if(!this.audioRoutingManager) {
|
||||
return;
|
||||
}
|
||||
let desc: audio.AudioDeviceDescriptors =
|
||||
this.audioRoutingManager.getPreferredOutputDeviceForRendererInfoSync(this.audioRendererInfo);
|
||||
this.changePickerShow(desc); //第一次拉起picker时获取当前输出设备
|
||||
this.audioRoutingManager.on('preferOutputDeviceChangeForRendererInfo', this.audioRendererInfo, (desc: audio.AudioDeviceDescriptors) => {
|
||||
if(!this.audioRoutingManager) {
|
||||
return;
|
||||
}
|
||||
console.log(`device change to: ${desc[0].deviceType}`);
|
||||
let devices: audio.AudioDeviceDescriptors =
|
||||
this.audioRoutingManager.getPreferredOutputDeviceForRendererInfoSync(this.audioRendererInfo);
|
||||
this.changePickerShow(devices);
|
||||
});
|
||||
}
|
||||
|
||||
private changePickerShow(desc: audio.AudioDeviceDescriptors) {
|
||||
if (desc[0].deviceType === 2) {
|
||||
this.pickerImage = $r('app.media.ic_public_sound');
|
||||
} else if (desc[0].deviceType === 7) {
|
||||
this.pickerImage = $r('app.media.ic_bluetooth');
|
||||
} else {
|
||||
this.pickerImage = $r('app.media.ic_earpiece');
|
||||
}
|
||||
}
|
||||
|
||||
async getStageFileDescriptor(fileName: string): Promise<resourceManager.RawFileDescriptor | undefined> {
|
||||
let fileDescriptor: resourceManager.RawFileDescriptor | undefined = undefined;
|
||||
if (this.appContext) {
|
||||
let mgr = this.appContext.resourceManager;
|
||||
await mgr.getRawFd(fileName).then(value => {
|
||||
fileDescriptor = value;
|
||||
console.log('case getRawFileDescriptor success fileName: ' + fileName);
|
||||
}).catch((error: BusinessError) => {
|
||||
console.log('case getRawFileDescriptor err: ' + error);
|
||||
});
|
||||
}
|
||||
return fileDescriptor;
|
||||
}
|
||||
|
||||
async startRenderer(): Promise<void> {
|
||||
if (this.audioRenderer !== undefined) {
|
||||
return;
|
||||
}
|
||||
this.getStageFileDescriptor(this.audioSource).then((res) => {
|
||||
this.fileDescriptor = res;
|
||||
});
|
||||
try {
|
||||
this.audioRenderer = await audio.createAudioRenderer(this.audioRendererOption);
|
||||
} catch (error) {
|
||||
console.error(`audioRenderer create : Error: ${JSON.stringify(error)}`);
|
||||
return;
|
||||
}
|
||||
let bufferSize : number;
|
||||
try {
|
||||
bufferSize = await this.audioRenderer.getBufferSize();
|
||||
await this.audioRenderer.start();
|
||||
} catch (err) {
|
||||
let error = err as BusinessError;
|
||||
console.error(`audioRenderer start : Error: ${JSON.stringify(error)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!this.fileDescriptor) {
|
||||
return;
|
||||
}
|
||||
let startOffset = this.fileDescriptor.offset;
|
||||
let cur = startOffset;
|
||||
let buf = new ArrayBuffer(bufferSize);
|
||||
while (true) {
|
||||
// when render released,state is changed to STATE_RELEASED
|
||||
if (this.audioRenderer.state === audio.AudioState.STATE_RELEASED) {
|
||||
break;
|
||||
}
|
||||
while (cur <= startOffset + this.fileDescriptor.length) {
|
||||
// when render released,state is changed to STATE_RELEASED
|
||||
if (this.audioRenderer.state.valueOf() === audio.AudioState.STATE_RELEASED.valueOf()) {
|
||||
break;
|
||||
}
|
||||
let options: Options = {
|
||||
offset: cur,
|
||||
length: bufferSize
|
||||
}
|
||||
await fs.read(this.fileDescriptor.fd, buf, options);
|
||||
await this.audioRenderer.write(buf);
|
||||
cur += bufferSize;
|
||||
}
|
||||
cur = startOffset;
|
||||
}
|
||||
} catch (err) {
|
||||
let error = err as BusinessError;
|
||||
console.error(`audioRenderer write : Error: ${JSON.stringify(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async stopRenderer(): Promise<void> {
|
||||
if (this.audioRenderer) {
|
||||
await this.audioRenderer.release();
|
||||
this.audioRenderer = undefined;
|
||||
}
|
||||
if (this.fileDescriptor) {
|
||||
this.closeResource(this.audioSource);
|
||||
this.fileDescriptor = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async closeResource(fileName: string): Promise<void> {
|
||||
if (this.appContext) {
|
||||
let mgr = this.appContext.resourceManager;
|
||||
await mgr.closeRawFd(fileName).then(() => {
|
||||
console.log('case closeRawFd success fileName: ' + fileName);
|
||||
}).catch((error: BusinessError) => {
|
||||
console.log('case closeRawFd err: ' + error);
|
||||
});
|
||||
}
|
||||
}
|
||||
onBackPress(): void {
|
||||
this.stopRenderer();
|
||||
this.appContext ==undefined;
|
||||
}
|
||||
|
||||
async onPageHide(): Promise<void> {
|
||||
this.stopRenderer();
|
||||
this.appContext ==undefined;
|
||||
}
|
||||
|
||||
onPageShow(): void {
|
||||
this.init();
|
||||
}
|
||||
|
||||
aboutToDisappear() {
|
||||
console.log('about to disappear');
|
||||
this.appContext = undefined;
|
||||
try {
|
||||
if (this.session) {
|
||||
this.session?.destroy();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('case closeRawFd err: ' + error);
|
||||
}
|
||||
}
|
||||
|
||||
@Builder
|
||||
ImangeBuilder(): void {
|
||||
Image(this.pickerImage)
|
||||
.size({ width: '100%', height: '100%' })
|
||||
.backgroundColor('#00000000')
|
||||
.fillColor(Color.Black)
|
||||
}
|
||||
|
||||
build() {
|
||||
Row() {
|
||||
Column() {
|
||||
Button() {
|
||||
Text('start').fontSize(22).fontColor(Color.White)
|
||||
}
|
||||
.size({ width: 64, height: 64 })
|
||||
.onClick(() => {
|
||||
this.startRenderer();
|
||||
})
|
||||
.type(ButtonType.Circle)
|
||||
}
|
||||
.size({ width: '33%', height: 64 })
|
||||
|
||||
Column() {
|
||||
Button() {
|
||||
Text('stop').fontSize(22).fontColor(Color.White)
|
||||
}
|
||||
.size({ width: 64, height: 64 })
|
||||
.onClick(() => {
|
||||
this.stopRenderer();
|
||||
})
|
||||
.type(ButtonType.Circle)
|
||||
}
|
||||
.size({ width: '33%', height: 64 })
|
||||
|
||||
Column() {
|
||||
Button() {
|
||||
AVCastPicker({
|
||||
normalColor: this.avCastPickerColor,
|
||||
activeColor: this.avCastPickerColor,
|
||||
customPicker: (): void => this.ImangeBuilder(),
|
||||
onStateChange: (state: AVCastPickerState) => {
|
||||
console.error(`change avcastpicker: ${state}`);
|
||||
}
|
||||
})
|
||||
.size({ width: 45, height: 45 })
|
||||
}
|
||||
.size({ width: 64, height: 64 })
|
||||
.type(ButtonType.Circle)
|
||||
.backgroundColor(Color.Orange)
|
||||
}
|
||||
.size({ width: '33%', height: 64 })
|
||||
}
|
||||
.margin({ top: 300})
|
||||
.justifyContent(FlexAlign.SpaceBetween)
|
||||
}
|
||||
}
|
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 { audio } from '@kit.AudioKit';
|
||||
import fs from '@ohos.file.fs';
|
||||
import { BusinessError } from '@ohos.base';
|
||||
import resourceManager from '@ohos.resourceManager';
|
||||
import common from '@ohos.app.ability.common';
|
||||
import { avSession, AVCastPicker } from '@kit.AVSessionKit';
|
||||
|
||||
class Options {
|
||||
public offset: number = 0;
|
||||
public length: number = 0;
|
||||
}
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct DefaultPicker {
|
||||
@State session: avSession.AVSession | undefined = undefined;
|
||||
@State avCastPickerColor:Color = Color.White;
|
||||
private appContext?: common.Context | undefined = undefined;
|
||||
private audioRenderer: audio.AudioRenderer | undefined = undefined;
|
||||
private audioSource = 'test1.wav';
|
||||
private fileDescriptor?: resourceManager.RawFileDescriptor | undefined = undefined;
|
||||
private audioRendererInfo: audio.AudioRendererInfo = {
|
||||
usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION,
|
||||
rendererFlags: 0
|
||||
}
|
||||
private audioStreamInfo: audio.AudioStreamInfo = {
|
||||
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率
|
||||
channels: audio.AudioChannel.CHANNEL_2, // 通道
|
||||
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
|
||||
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
|
||||
}
|
||||
private audioRendererOption: audio.AudioRendererOptions = {
|
||||
streamInfo: this.audioStreamInfo,
|
||||
rendererInfo: this.audioRendererInfo
|
||||
}
|
||||
|
||||
async aboutToAppear() {
|
||||
console.log('about to appear');
|
||||
await this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this.appContext) {
|
||||
this.appContext = getContext();
|
||||
}
|
||||
this.session = await avSession.createAVSession(this.appContext, 'voiptest', 'voice_call');
|
||||
this.observerDevices();
|
||||
}
|
||||
|
||||
async observerDevices() {
|
||||
let audioManager = audio.getAudioManager();
|
||||
let audioRoutingManager = audioManager.getRoutingManager();
|
||||
audioRoutingManager.on('preferOutputDeviceChangeForRendererInfo', this.audioRendererInfo, (desc: audio.AudioDeviceDescriptors) => {
|
||||
console.log(`device change to: ${desc[0].deviceType}`);
|
||||
});
|
||||
}
|
||||
|
||||
async getStageFileDescriptor(fileName: string): Promise<resourceManager.RawFileDescriptor | undefined> {
|
||||
let fileDescriptor: resourceManager.RawFileDescriptor | undefined = undefined;
|
||||
if (this.appContext) {
|
||||
let mgr = this.appContext.resourceManager;
|
||||
await mgr.getRawFd(fileName).then(value => {
|
||||
fileDescriptor = value;
|
||||
console.log('case getRawFileDescriptor success fileName: ' + fileName);
|
||||
}).catch((error: BusinessError) => {
|
||||
console.log('case getRawFileDescriptor err: ' + error);
|
||||
});
|
||||
}
|
||||
return fileDescriptor;
|
||||
}
|
||||
|
||||
async startRenderer(): Promise<void> {
|
||||
if (this.audioRenderer !== undefined) {
|
||||
return;
|
||||
}
|
||||
this.getStageFileDescriptor(this.audioSource).then((res) => {
|
||||
this.fileDescriptor = res;
|
||||
});
|
||||
try {
|
||||
this.audioRenderer = await audio.createAudioRenderer(this.audioRendererOption);
|
||||
} catch (error) {
|
||||
console.error(`audioRenderer create : Error: ${JSON.stringify(error)}`);
|
||||
return;
|
||||
}
|
||||
let bufferSize : number;
|
||||
try {
|
||||
bufferSize = await this.audioRenderer.getBufferSize();
|
||||
await this.audioRenderer.start();
|
||||
} catch (err) {
|
||||
let error = err as BusinessError;
|
||||
console.error(`audioRenderer start : Error: ${JSON.stringify(error)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!this.fileDescriptor) {
|
||||
return;
|
||||
}
|
||||
let startOffset = this.fileDescriptor.offset;
|
||||
let cur = startOffset;
|
||||
let buf = new ArrayBuffer(bufferSize);
|
||||
while (true) {
|
||||
// when render released,state is changed to STATE_RELEASED
|
||||
if (this.audioRenderer.state === audio.AudioState.STATE_RELEASED) {
|
||||
break;
|
||||
}
|
||||
while (cur <= startOffset + this.fileDescriptor.length) {
|
||||
// when render released,state is changed to STATE_RELEASED
|
||||
if (this.audioRenderer.state.valueOf() === audio.AudioState.STATE_RELEASED.valueOf()) {
|
||||
break;
|
||||
}
|
||||
let options: Options = {
|
||||
offset: cur,
|
||||
length: bufferSize
|
||||
}
|
||||
await fs.read(this.fileDescriptor.fd, buf, options);
|
||||
await this.audioRenderer.write(buf);
|
||||
cur += bufferSize;
|
||||
}
|
||||
cur = startOffset;
|
||||
}
|
||||
} catch (err) {
|
||||
let error = err as BusinessError;
|
||||
console.error(`audioRenderer write : Error: ${JSON.stringify(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async stopRenderer(): Promise<void> {
|
||||
if (this.audioRenderer) {
|
||||
await this.audioRenderer.release();
|
||||
this.audioRenderer = undefined;
|
||||
}
|
||||
if (this.fileDescriptor) {
|
||||
this.closeResource(this.audioSource);
|
||||
this.fileDescriptor = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async closeResource(fileName: string): Promise<void> {
|
||||
if (this.appContext) {
|
||||
let mgr = this.appContext.resourceManager;
|
||||
await mgr.closeRawFd(fileName).then(() => {
|
||||
console.log('case closeRawFd success fileName: ' + fileName);
|
||||
}).catch((error: BusinessError) => {
|
||||
console.log('case closeRawFd err: ' + error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onBackPress(): void {
|
||||
this.stopRenderer();
|
||||
this.appContext ==undefined;
|
||||
}
|
||||
|
||||
async onPageHide(): Promise<void> {
|
||||
this.stopRenderer();
|
||||
this.appContext ==undefined;
|
||||
}
|
||||
|
||||
onPageShow(): void {
|
||||
this.init();
|
||||
}
|
||||
|
||||
aboutToDisappear() {
|
||||
console.log('about to disappear');
|
||||
this.appContext = undefined;
|
||||
try {
|
||||
if (this.session) {
|
||||
this.session?.destroy();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('case closeRawFd err: ' + error);
|
||||
}
|
||||
}
|
||||
|
||||
build() {
|
||||
Row() {
|
||||
Column() {
|
||||
Button() {
|
||||
Text('start').fontSize(22).fontColor(Color.White)
|
||||
}
|
||||
.size({ width: 64, height: 64 })
|
||||
.onClick(() => {
|
||||
this.startRenderer();
|
||||
})
|
||||
.type(ButtonType.Circle)
|
||||
}
|
||||
.size({ width: '33%', height: 64 })
|
||||
|
||||
Column() {
|
||||
Button() {
|
||||
Text('stop').fontSize(22).fontColor(Color.White)
|
||||
}
|
||||
.size({ width: 64, height: 64 })
|
||||
.onClick(() => {
|
||||
this.stopRenderer();
|
||||
})
|
||||
.type(ButtonType.Circle)
|
||||
}
|
||||
.size({ width: '33%', height: 64 })
|
||||
|
||||
Column() {
|
||||
Button() {
|
||||
AVCastPicker({
|
||||
normalColor: this.avCastPickerColor,
|
||||
activeColor: this.avCastPickerColor,
|
||||
})
|
||||
.size({ width: 45, height: 45 })
|
||||
}
|
||||
.size({ width: 64, height: 64 })
|
||||
.type(ButtonType.Circle)
|
||||
}
|
||||
.size({ width: '33%', height: 64 })
|
||||
}
|
||||
.margin({ top: 300})
|
||||
.justifyContent(FlexAlign.SpaceBetween)
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 router from '@ohos.router';
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct Index {
|
||||
async aboutToAppear() {
|
||||
console.log('about to appear');
|
||||
}
|
||||
|
||||
build() {
|
||||
Row({ space: 10}) {
|
||||
Column({ space: 50}) {
|
||||
Button() {
|
||||
Text($r('app.string.default_avcastpicker'))
|
||||
.fontSize(22)
|
||||
.fontColor(Color.White)
|
||||
.alignRules({
|
||||
center: {anchor: '__container__', align: VerticalAlign.Center},
|
||||
middle: {anchor: '__container__', align: HorizontalAlign.Center}
|
||||
})
|
||||
}
|
||||
.size({ width: '50%', height: 60})
|
||||
.onClick(async() => {
|
||||
await router.pushUrl({ url:'pages/DefaultPicker' });
|
||||
})
|
||||
|
||||
Button() {
|
||||
Text($r('app.string.custom_avcastpicker'))
|
||||
.fontSize(22)
|
||||
.fontColor(Color.White)
|
||||
.alignRules({
|
||||
center: {anchor: '__container__', align: VerticalAlign.Center},
|
||||
middle: {anchor: '__container__', align: HorizontalAlign.Center}
|
||||
})
|
||||
}
|
||||
.size({ width: '50%', height: 60})
|
||||
.onClick(async() => {
|
||||
await router.pushUrl({ url:'pages/CustomPicker' });
|
||||
})
|
||||
}
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.alignItems(HorizontalAlign.Center)
|
||||
.size({ width: '100%', height: '100%'})
|
||||
}
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.size({ width: '100%', height: '100%'})
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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": [
|
||||
"phone",
|
||||
"tablet"
|
||||
],
|
||||
"deliveryWithInstall": true,
|
||||
"installationFree": false,
|
||||
"pages": "$profile:main_pages",
|
||||
"abilities": [
|
||||
{
|
||||
"name": "EntryAbility",
|
||||
"srcEntry": "./ets/entryability/EntryAbility.ts",
|
||||
"description": "$string:EntryAbility_desc",
|
||||
"icon": "$media:icon",
|
||||
"label": "$string:EntryAbility_label",
|
||||
"startWindowIcon": "$media:startIcon",
|
||||
"startWindowBackground": "$color:start_window_background",
|
||||
"exported": true,
|
||||
"skills": [
|
||||
{
|
||||
"entities": [
|
||||
"entity.system.home"
|
||||
],
|
||||
"actions": [
|
||||
"action.system.home"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"color": [
|
||||
{
|
||||
"name": "start_window_background",
|
||||
"value": "#FFFFFF"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "module_desc",
|
||||
"value": "module description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_desc",
|
||||
"value": "description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_label",
|
||||
"value": "label"
|
||||
},
|
||||
{
|
||||
"name": "default_avcastpicker",
|
||||
"value": "default_avcastpicker"
|
||||
},
|
||||
{
|
||||
"name": "custom_avcastpicker",
|
||||
"value": "custom_avcastpicker"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Public/ic_public_bluetooth</title>
|
||||
<defs>
|
||||
<path d="M13.768,12.7072498 L17.488084,16.4274238 C18.0588502,16.9981901 18.0734853,17.914492 17.5319891,18.502978 L17.4642578,18.5720467 L13.536834,22.328713 C12.9381779,22.9013406 11.9886648,22.8802403 11.4160372,22.2815842 C11.165714,22.0198827 11.0190891,21.677103 11.0017388,21.3169859 L11,21.2447502 L11,15.4742498 L12.5,13.9742498 L12.5,21.2447502 L16.4274238,17.488084 L12.707,13.7672498 L13.768,12.7072498 Z M8.3329433,16.7276367 C8.33317605,16.7279641 8.33340872,16.7282916 8.3336413,16.7286191 L6.53033009,18.5303301 C6.2491526,18.8115076 5.80026186,18.8227547 5.50566731,18.5640714 L5.46966991,18.5303301 C5.18849242,18.2491526 5.17724532,17.8002619 5.43592862,17.5056673 L5.46966991,17.4696699 L10.93925,12 L5.46966991,6.53033009 C5.18849242,6.2491526 5.17724532,5.80026186 5.43592862,5.50566731 L5.46966991,5.46966991 C5.7508474,5.18849242 6.19973814,5.17724532 6.49433269,5.43592862 L6.53033009,5.46966991 L11,9.93925 L11,2.75524981 C11,1.94565058 11.6413937,1.28585687 12.4437654,1.25628443 L12.5,1.25524981 C12.8621455,1.25524981 13.2112029,1.38622955 13.4834315,1.62261261 L13.536834,1.67128703 L17.4642578,5.42795326 C18.0475637,5.98589809 18.0825524,6.90164871 17.5542641,7.50201978 L17.4967734,7.56380922 Z M12.5,2.75524981 L12.5,10.439 L16.4274238,6.51191604 L12.5,2.75524981 Z" id="path-1"></path>
|
||||
</defs>
|
||||
<g id="Public/ic_public_bluetooth" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<use id="形状结合" fill="#000000" fill-rule="nonzero" xlink:href="#path-1"></use>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>ic_Earpiece Emergency</title>
|
||||
<defs>
|
||||
<path d="M13.5443725,2.18811098 C16.8500206,2.97088845 19.0120499,5.97690286 18.7125453,9.3117657 C18.6754938,9.7243188 18.3110173,10.028723 17.8984642,9.99167156 C17.4859111,9.9546201 17.1815069,9.59014359 17.2185583,9.17759048 C17.4513918,6.58508348 15.7780533,4.2585287 13.1990029,3.6478095 C10.337219,2.97126433 7.56962653,4.67974032 6.89184912,7.54183236 C6.66446864,8.50156381 6.71047655,9.67930698 7.02958924,10.8232323 C7.33736732,11.9344354 7.74026266,12.6689241 8.50366801,13.6486212 L8.78295466,13.9990113 L9.22612684,14.5448222 L9.4446938,14.8216904 C9.47723478,14.8640221 9.5083145,14.9050966 9.53865621,14.9458875 C10.2305333,15.8760364 10.557322,16.7462243 10.6787993,18.1906352 C10.828294,19.8563165 11.8489297,20.4876781 13.4543018,20.4876781 C14.6213106,20.4876781 15.5752616,19.6530123 16.0352293,18.7347726 C16.2207447,18.3644257 16.6713603,18.2145901 17.0417073,18.4001056 C17.4120542,18.585621 17.5618898,19.0362366 17.3763743,19.4065835 C16.689287,20.7782253 15.3069868,21.9876781 13.4543018,21.9876781 C11.1613167,21.9876781 9.41669469,20.9084612 9.18444074,18.3205385 C9.08592832,17.1493021 8.8593203,16.54588 8.33510393,15.8411316 L8.1625789,15.6168123 L7.53390367,14.8398231 C6.52269673,13.5841247 5.981075,12.6571728 5.58438481,11.2249564 C5.19993984,9.84683974 5.14392761,8.4129989 5.43223665,7.19609919 C6.30084717,3.5281645 9.87633591,1.32096213 13.5443725,2.18811098 Z M12.6275873,6.32364094 C13.9718083,6.63329863 14.8622731,7.8377126 14.7386547,9.17673281 C14.7005764,9.58919241 14.3353433,9.89268842 13.9228837,9.85461014 C13.5447957,9.81970504 13.2582675,9.50989901 13.2424683,9.14080816 L13.2450064,9.03883914 C13.2989814,8.45418786 12.9099283,7.92796724 12.2911285,7.7854191 C11.5981013,7.62603693 10.9426591,8.019651 10.7827628,8.67649841 C10.7288709,8.89778239 10.7406035,9.18995735 10.8236847,9.47968609 C10.8975257,9.73903379 10.9894535,9.91482015 11.1669964,10.1461482 L11.3481024,10.3693152 C11.6135328,10.6873083 11.5709222,11.1602664 11.2529291,11.4256967 C10.934936,11.6911271 10.4619779,11.6485165 10.1965475,11.3305234 L10.1288658,11.2492206 C9.75062036,10.7922918 9.5361085,10.435149 9.38140876,9.89180016 C9.2295105,9.3620951 9.20720817,8.80670532 9.32534269,8.32163703 C9.68135003,6.85917374 11.1420247,5.98199167 12.6275873,6.32364094 Z" id="path-1"></path>
|
||||
</defs>
|
||||
<g id="ic_Earpiece-Emergency" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<use id="形状结合" fill="#000000" fill-rule="nonzero" xlink:href="#path-1"></use>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Public/ic_public_sound</title>
|
||||
<defs>
|
||||
<path d="M19.6297132,3.97021633 C21.767174,6.11208412 22.9900393,9.00753275 22.9900393,12.0946605 C22.9900393,15.1739324 21.7734214,18.0626391 19.6455849,20.2031692 C19.3535641,20.4969322 18.8786924,20.4983444 18.5849295,20.2063235 C18.2911665,19.9143026 18.2897543,19.4394309 18.5817752,19.145668 C20.4329978,17.2834019 21.4900393,14.7735894 21.4900393,12.0946605 C21.4900393,9.4089026 20.4275654,6.89322181 18.5679613,5.02978367 C18.2753698,4.73658898 18.2758589,4.2617155 18.5690535,3.96912406 C18.8622482,3.67653262 19.3371217,3.67702165 19.6297132,3.97021633 Z M7.67146054,5.77188408 C8.4325828,5.17622318 9.53247286,5.31035612 10.1281338,6.07147837 C10.3690847,6.37936011 10.5,6.75905937 10.5,7.15001783 L10.5,7.15001783 L10.5,17.045134 C10.5,18.0116323 9.71649831,18.795134 8.75,18.795134 C8.35904154,18.795134 7.97934229,18.6642187 7.67138834,18.4232113 L7.67138834,18.4232113 L4.38,15.8465759 L2.25,15.8475759 C1.05913601,15.8475759 0.084355084,14.9224143 0.00519081254,13.7516247 L0.00519081254,13.7516247 L0,13.5975759 L0,10.5975759 C0,9.35493524 1.00735931,8.34757593 2.24981101,8.34757595 L2.24981101,8.34757595 L4.379,8.34657593 Z M16.4901783,6.49794378 C17.9618152,7.97261488 18.8039928,9.96668727 18.8039928,12.0921781 C18.8039928,14.2122592 17.966117,16.2016903 16.5011051,17.6754418 C16.2090842,17.9692047 15.7342125,17.9706169 15.4404496,17.678596 C15.1466866,17.3865751 15.1452744,16.9117035 15.4372953,16.6179406 C16.6256934,15.4224531 17.3039928,13.8119162 17.3039928,12.0921781 C17.3039928,10.3680571 16.6222066,8.75375258 15.4284264,7.55751111 C15.1358349,7.26431643 15.136324,6.78944294 15.4295187,6.4968515 C15.7227133,6.20426006 16.1975868,6.20474909 16.4901783,6.49794378 Z M8.94687625,6.99594076 C8.86178184,6.88720901 8.70465469,6.86804716 8.59586895,6.95318383 L8.59586895,6.95318383 L4.89763507,9.84690879 L2.25,9.84757593 C1.83578644,9.84757593 1.5,10.1833624 1.5,10.5975759 L1.5,10.5975759 L1.5,13.5975759 C1.5,14.0117895 1.83578644,14.3475759 2.24981101,14.347576 L2.24981101,14.347576 L4.89755819,14.3469088 L8.59592293,17.2420103 C8.63990604,17.2764318 8.69414879,17.295134 8.75,17.295134 C8.88807119,17.295134 9,17.1832052 9,17.045134 L9,17.045134 L9,7.15001783 C9,7.09416662 8.98129781,7.03992387 8.94687625,6.99594076 Z M13.6408603,9.2089851 C14.3913218,9.97869506 14.819589,11.0098911 14.819589,12.1067087 C14.819589,13.2171715 14.3805594,14.2600877 13.613475,15.0322585 C13.3215512,15.326118 12.8466801,15.3276871 12.5528206,15.0357634 C12.2589611,14.7438396 12.2573919,14.2689685 12.5493157,13.975109 C13.0399975,13.4811736 13.319589,12.817003 13.319589,12.1067087 C13.319589,11.4051196 13.0468694,10.7484563 12.5668561,10.2561312 C12.2776947,9.9595531 12.2837071,9.48471743 12.5802852,9.19555603 C12.8768633,8.90639462 13.3516989,8.91240702 13.6408603,9.2089851 Z" id="path-1"></path>
|
||||
</defs>
|
||||
<g id="Public/ic_public_sound" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<use id="合并形状" fill="#000000" fill-rule="nonzero" xlink:href="#path-1"></use>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 4.2 KiB |
@ -0,0 +1,7 @@
|
||||
{
|
||||
"src": [
|
||||
"pages/Index",
|
||||
"pages/DefaultPicker",
|
||||
"pages/CustomPicker"
|
||||
]
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "module_desc",
|
||||
"value": "module description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_desc",
|
||||
"value": "description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_label",
|
||||
"value": "label"
|
||||
},
|
||||
{
|
||||
"name": "default_avcastpicker",
|
||||
"value": "default_avcastpicker"
|
||||
},
|
||||
{
|
||||
"name": "custom_avcastpicker",
|
||||
"value": "custom_avcastpicker"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "module_desc",
|
||||
"value": "模块描述"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_desc",
|
||||
"value": "description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_label",
|
||||
"value": "label"
|
||||
},
|
||||
{
|
||||
"name": "default_avcastpicker",
|
||||
"value": "默认样式组件"
|
||||
},
|
||||
{
|
||||
"name": "custom_avcastpicker",
|
||||
"value": "自定义样式组件"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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';
|
||||
|
||||
export default function abilityTest() {
|
||||
describe('ActsAbilityTest', () => {
|
||||
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
|
||||
beforeAll(() => {
|
||||
// Presets an action, which is performed only once before all test cases of the test suite start.
|
||||
// This API supports only one parameter: preset action function.
|
||||
})
|
||||
beforeEach(() => {
|
||||
// Presets an action, which is performed before each unit test case starts.
|
||||
// The number of execution times is the same as the number of test cases defined by **it**.
|
||||
// This API supports only one parameter: preset action function.
|
||||
})
|
||||
afterEach(() => {
|
||||
// Presets a clear action, which is performed after each unit test case ends.
|
||||
// The number of execution times is the same as the number of test cases defined by **it**.
|
||||
// This API supports only one parameter: clear action function.
|
||||
})
|
||||
afterAll(() => {
|
||||
// Presets a clear action, which is performed after all test cases of the test suite end.
|
||||
// This API supports only one parameter: clear action function.
|
||||
})
|
||||
it('assertContain', 0, () => {
|
||||
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
|
||||
let a = 'abc';
|
||||
let b = 'b';
|
||||
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
|
||||
expect(a).assertContain(b);
|
||||
expect(a).assertEqual(a);
|
||||
})
|
||||
})
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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();
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
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;
|
||||
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
|
||||
let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs;
|
||||
abilityDelegatorArguments = 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');
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.
|
||||
*/
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct Index {
|
||||
@State message: string = 'Hello World';
|
||||
|
||||
build() {
|
||||
Row() {
|
||||
Column() {
|
||||
Text(this.message)
|
||||
.fontSize(50)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.height('100%')
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 TestRunner from '@ohos.application.testRunner';
|
||||
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
|
||||
import Want from '@ohos.app.ability.Want';
|
||||
|
||||
let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator | undefined = undefined
|
||||
let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs | undefined = undefined
|
||||
|
||||
async function onAbilityCreateCallback() {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback');
|
||||
}
|
||||
|
||||
async function addAbilityMonitorCallback(err : Error) {
|
||||
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: AbilityDelegatorRegistry.AbilityMonitor = {
|
||||
abilityName: testAbilityName,
|
||||
onAbilityCreate: onAbilityCreateCallback,
|
||||
};
|
||||
abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback)
|
||||
const want: Want = {
|
||||
bundleName: bundleName,
|
||||
abilityName: testAbilityName
|
||||
};
|
||||
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
|
||||
abilityDelegator.startAbility(want, (err, data) => {
|
||||
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');
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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_test",
|
||||
"type": "feature",
|
||||
"description": "$string:module_test_desc",
|
||||
"mainElement": "TestAbility",
|
||||
"deviceTypes": [
|
||||
"phone",
|
||||
"tablet"
|
||||
],
|
||||
"deliveryWithInstall": true,
|
||||
"installationFree": false,
|
||||
"pages": "$profile:test_pages",
|
||||
"abilities": [
|
||||
{
|
||||
"name": "TestAbility",
|
||||
"srcEntry": "./ets/testability/TestAbility.ets",
|
||||
"description": "$string:TestAbility_desc",
|
||||
"icon": "$media:icon",
|
||||
"label": "$string:TestAbility_label",
|
||||
"exported": true,
|
||||
"startWindowIcon": "$media:icon",
|
||||
"startWindowBackground": "$color:start_window_background",
|
||||
"skills": [
|
||||
{
|
||||
"actions": [
|
||||
"action.system.home"
|
||||
],
|
||||
"entities": [
|
||||
"entity.system.home"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"color": [
|
||||
{
|
||||
"name": "start_window_background",
|
||||
"value": "#FFFFFF"
|
||||
}
|
||||
]
|
||||
}
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 6.6 KiB |
@ -0,0 +1,5 @@
|
||||
{
|
||||
"src": [
|
||||
"testability/pages/Index"
|
||||
]
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.
|
||||
*/
|
||||
|
||||
{
|
||||
"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 */
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
|
||||
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. */
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
# ----------------------------------------------------------------------------
|
||||
# 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.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 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}" "$@"
|
@ -0,0 +1,77 @@
|
||||
:: Copyright (c) 2024 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.
|
||||
|
||||
@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
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.
|
||||
*/
|
||||
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
|
||||
"specifiers": {
|
||||
"@ohos/hypium@1.0.11": "@ohos/hypium@1.0.11"
|
||||
},
|
||||
"packages": {
|
||||
"@ohos/hypium@1.0.11": {
|
||||
"resolved": "https://repo.harmonyos.com/ohpm/@ohos/hypium/-/hypium-1.0.11.tgz",
|
||||
"integrity": "sha512-KawcLnv43C3QIYv1UbDnKCFX3MohtDxGuFvzlUxT/qf2DBilR56Ws6zrj90LdH6PjloJQwOPESuBQIHBACAK7w=="
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.
|
||||
*/
|
||||
|
||||
{
|
||||
"name": "avcastpickerforcall",
|
||||
"version": "1.0.0",
|
||||
"description": "Please describe the basic information.",
|
||||
"main": "",
|
||||
"author": "",
|
||||
"license": "",
|
||||
"dependencies": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohos/hypium": "1.0.11"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 29 KiB |
@ -34,6 +34,6 @@ N/A
|
||||
|
||||
1. This sample can only be run on standard-system devices that use the Rockchip RK3568 chip.
|
||||
|
||||
2. This sample is based on the stage model, which is supported from API version 11 (API Version 11 4.1.3.5).
|
||||
2. This sample is based on the stage model, which is supported from API version 12 (API Version 12 5.0.0.26).
|
||||
|
||||
3. DevEco Studio (3.1.0.501) must be used.
|
||||
3. DevEco Studio (4.1.3.500) must be used.
|
||||
|
@ -98,8 +98,8 @@ photomodify/src/main/ets/components
|
||||
### 约束与限制
|
||||
|
||||
1. 本示例仅支持标准系统上运行,支持设备:RK3568;
|
||||
2. 本示例为Stage模型,已适配API version 11版本SDK,SDK版本号(API Version 11 4.1.3.5),镜像版本号(4.1.3.5);
|
||||
3. 本示例需要使用DevEco Studio 版本号(3.1.0.501)及以上版本才可编译运行。
|
||||
2. 本示例为Stage模型,已适配API version 12版本SDK,SDK版本号(API Version 12 5.0.0.26),镜像版本号(5.0.0.26);
|
||||
3. 本示例需要使用DevEco Studio 版本号(4.1.3.500)及以上版本才可编译运行。
|
||||
|
||||
### 下载
|
||||
|
||||
|
@ -26,11 +26,11 @@ const BUNDLE: string = 'Image_';
|
||||
async function enterEdit() {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'ClickPhoto_001 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.delayMs(3000);
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('Photo'));
|
||||
let photo = await driver.findComponent(ON.id('Photo'));
|
||||
await photo.click();
|
||||
await driver.delayMs(3000);
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('Edit'));
|
||||
let stack = await driver.findComponent(ON.id('Edit'));
|
||||
await stack.click();
|
||||
@ -38,6 +38,27 @@ async function enterEdit() {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'ClickPhoto_001 end');
|
||||
}
|
||||
|
||||
async function saveEdit() {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Save_001 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.assertComponentExist(ON.id('Save'));
|
||||
let scale = await driver.findComponent(ON.id('Save'));
|
||||
await scale.click();
|
||||
await driver.delayMs(1000);
|
||||
// SDK升级到12后,出现保存后加载的弹窗不能自动关闭,导致保存后以后的case找不到控件
|
||||
// 对应解决方法:点击加载后不能关闭的弹窗外的区域,使这个弹窗关闭
|
||||
await driver.delayMs(1000);
|
||||
// 定位不能关闭的弹窗
|
||||
await driver.assertComponentExist(ON.type('Dialog'));
|
||||
let loadingDig = await driver.findComponent(ON.type('Dialog'));
|
||||
// 获取弹窗中心位置
|
||||
let point = await loadingDig.getBoundsCenter();
|
||||
// 点击弹窗顶部空白区域,使其关闭
|
||||
await driver.click(point.x, point.y + 200);
|
||||
await driver.delayMs(1000);
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Save_001 end');
|
||||
}
|
||||
|
||||
async function showColorSpace() {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'ShowColorSpace_001 begin');
|
||||
let driver = Driver.create();
|
||||
@ -246,11 +267,122 @@ export default function ActsAbilityTest() {
|
||||
await ok.click();
|
||||
await driver.delayMs(1000);
|
||||
// 保存当前操作
|
||||
//await saveEdit();
|
||||
await saveEdit();
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'CropOrigin end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number ScalePhoto_001
|
||||
* @tc.name ScalePhoto_001
|
||||
* @tc.desc 点击缩放
|
||||
*/
|
||||
it('ScalePhoto_001', 8, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'ScalePhoto_001 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
// 进入编辑
|
||||
await enterEdit();
|
||||
let tip: string = getString($r('app.string.scale'));
|
||||
await driver.assertComponentExist(ON.text(tip));
|
||||
let stack = await driver.findComponent(ON.text(tip));
|
||||
await stack.click();
|
||||
await driver.delayMs(1000);
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'ScalePhoto_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number Scale_001
|
||||
* @tc.name Scale_001
|
||||
* @tc.desc 点击缩小
|
||||
*/
|
||||
it('Scale_001', 9, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Scale_001 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
let zoomOut: string = getString($r('app.string.zoomOut'));
|
||||
await driver.assertComponentExist(ON.text(zoomOut));
|
||||
let stack = await driver.findComponent(ON.text(zoomOut));
|
||||
await stack.click();
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('Ok'));
|
||||
let ok = await driver.findComponent(ON.id('Ok'));
|
||||
await ok.click();
|
||||
await driver.delayMs(1000);
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Scale_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number Scale_002
|
||||
* @tc.name Scale_002
|
||||
* @tc.desc 点击放大
|
||||
*/
|
||||
it('Scale_002', 10, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Scale_002 begin');
|
||||
let driver = Driver.create();
|
||||
let tip = getString($r('app.string.scale'));
|
||||
await driver.assertComponentExist(ON.text(tip));
|
||||
let scale = await driver.findComponent(ON.text(tip));
|
||||
await scale.click();
|
||||
await driver.delayMs(1000);
|
||||
let zoomIn = getString($r('app.string.zoomIn'));
|
||||
await driver.assertComponentExist(ON.text(zoomIn));
|
||||
let stack = await driver.findComponent(ON.text(zoomIn));
|
||||
await stack.click();
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('Ok'));
|
||||
let ok = await driver.findComponent(ON.id('Ok'));
|
||||
await ok.click();
|
||||
await driver.delayMs(1000);
|
||||
// 保存当前编辑
|
||||
await saveEdit();
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Scale_002 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number RotatePhoto_001
|
||||
* @tc.name RotatePhoto_001
|
||||
* @tc.desc 点击旋转
|
||||
*/
|
||||
it('RotatePhoto_001', 11, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'RotatePhoto_001 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
// 进入编辑
|
||||
await enterEdit();
|
||||
let tip: string = getString($r('app.string.rotate'));
|
||||
await driver.assertComponentExist(ON.text(tip));
|
||||
let stack = await driver.findComponent(ON.text(tip));
|
||||
await stack.click();
|
||||
await driver.delayMs(1000);
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'RotatePhoto_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number Rotate_001
|
||||
* @tc.name Rotate_001
|
||||
* @tc.desc 旋转-90度
|
||||
*/
|
||||
it('Rotate_001', 12, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Rotate_001 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('-90'));
|
||||
let rotate90 = await driver.findComponent(ON.id('-90'));
|
||||
await rotate90.click();
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('Ok'));
|
||||
let ok = await driver.findComponent(ON.id('Ok'));
|
||||
await ok.click();
|
||||
await driver.delayMs(1000);
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Rotate_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number Rotate_002
|
||||
* @tc.name Rotate_002
|
||||
@ -273,11 +405,124 @@ export default function ActsAbilityTest() {
|
||||
await ok.click();
|
||||
await driver.delayMs(1000);
|
||||
// 保存当前编辑
|
||||
//await saveEdit();
|
||||
await saveEdit();
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Rotate_002 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number ToningPhoto_001
|
||||
* @tc.name ToningPhoto_001
|
||||
* @tc.desc 点击调色
|
||||
*/
|
||||
it('ToningPhoto_001', 14, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'ToningPhoto_001 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
// 进入编辑
|
||||
await enterEdit();
|
||||
let tip: string = getString($r('app.string.toning'));
|
||||
await driver.assertComponentExist(ON.text(tip));
|
||||
let stack = await driver.findComponent(ON.text(tip));
|
||||
await stack.click();
|
||||
await driver.delayMs(1000);
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'ToningPhoto_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number Toning_001
|
||||
* @tc.name Toning_001
|
||||
* @tc.desc 拖动滑动条
|
||||
*/
|
||||
it('Toning_001', 15, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Toning_001 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('Slider'));
|
||||
let slider = await driver.findComponent(ON.id('Slider'));
|
||||
let point = await slider.getBoundsCenter();
|
||||
// 拖动滑动条,固定坐标只支持RK3568,其他设备需要调整
|
||||
await driver.drag(point.x - 50, point.y, point.x + 50, point.y, 800);
|
||||
await slider.click();
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('Ok'));
|
||||
let ok = await driver.findComponent(ON.id('Ok'));
|
||||
await ok.click();
|
||||
await driver.delayMs(1000);
|
||||
// 保存当前编辑
|
||||
await saveEdit();
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Scale_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number Mark_001
|
||||
* @tc.name Mark_001
|
||||
* @tc.desc 点击标记
|
||||
*/
|
||||
it('Mark_001', 16, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Mark_001 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
// 进入编辑
|
||||
await enterEdit();
|
||||
let mark: string = getString($r('app.string.mark'));
|
||||
await driver.assertComponentExist(ON.text(mark));
|
||||
let stack = await driver.findComponent(ON.text(mark));
|
||||
await stack.click();
|
||||
await driver.delayMs(1000);
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Mark_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number Text_001
|
||||
* @tc.name Text_001
|
||||
* @tc.desc 点击文字
|
||||
*/
|
||||
it('Text_001', 17, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Text_001 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
let text: string = getString($r('app.string.text'));
|
||||
await driver.assertComponentExist(ON.text(text));
|
||||
let stack = await driver.findComponent(ON.text(text));
|
||||
await stack.click();
|
||||
await driver.delayMs(1000);
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Text_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number TextInput_001
|
||||
* @tc.name TextInput_001
|
||||
* @tc.desc 输入文字
|
||||
*/
|
||||
it('TextInput_001', 18, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'TextInput_001 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('TextInput'));
|
||||
let input = await driver.findComponent(ON.id('TextInput'));
|
||||
await input.inputText('ttt');
|
||||
await driver.delayMs(3000);
|
||||
await driver.assertComponentExist(ON.id('Sure'));
|
||||
let sure = await driver.findComponent(ON.id('Sure'));
|
||||
await sure.click();
|
||||
await driver.delayMs(1000);
|
||||
// SDK升级到12后,确定输入框文字后屏幕下方键盘控件不能自动关闭,导致以后的case找不到控件
|
||||
// 对应解决方法:确定输入框文字后,定位屏幕下方键盘控件关闭按钮的位置(x:709,y:694),点击键盘控件关闭按钮
|
||||
await driver.click(709,694)
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('Ok'));
|
||||
let ok = await driver.findComponent(ON.id('Ok'));
|
||||
await ok.click();
|
||||
await driver.delayMs(1000);
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'TextInput_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number Repeal_001
|
||||
* @tc.name Repeal_001
|
||||
@ -291,11 +536,76 @@ export default function ActsAbilityTest() {
|
||||
await scale.click();
|
||||
await driver.delayMs(1000);
|
||||
// 保存当前编辑
|
||||
//await saveEdit();
|
||||
await saveEdit();
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Repeal_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number Mark_002
|
||||
* @tc.name Mark_002
|
||||
* @tc.desc 点击标记
|
||||
*/
|
||||
it('Mark_002', 20, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Mark_002 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
// 进入编辑
|
||||
await enterEdit();
|
||||
let mark: string = getString($r('app.string.mark'));
|
||||
await driver.assertComponentExist(ON.text(mark));
|
||||
let stack = await driver.findComponent(ON.text(mark));
|
||||
await stack.click();
|
||||
await driver.delayMs(1000);
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Mark_002 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number AddSticker_001
|
||||
* @tc.name AddSticker_001
|
||||
* @tc.desc 添加贴纸
|
||||
*/
|
||||
it('AddSticker_001', 21, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'AddSticker_001 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
let sticker: string = getString($r('app.string.sticker'));
|
||||
await driver.assertComponentExist(ON.text(sticker));
|
||||
let stack = await driver.findComponent(ON.text(sticker));
|
||||
await stack.click();
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('Ok'));
|
||||
let ok = await driver.findComponent(ON.id('Ok'));
|
||||
await ok.click();
|
||||
await driver.delayMs(1000);
|
||||
// 保存当前编辑
|
||||
await saveEdit();
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'AddSticker_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number ColorSpace_001
|
||||
* @tc.name ColorSpace_001
|
||||
* @tc.desc 点击色域
|
||||
*/
|
||||
it('ColorSpace_001', 22, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'ColorSpace_001 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
// 进入编辑
|
||||
await enterEdit();
|
||||
await showColorSpace();
|
||||
let tip = getString($r('app.string.colorSpace'));
|
||||
await driver.assertComponentExist(ON.text(tip));
|
||||
let colorSpace = await driver.findComponent(ON.text(tip));
|
||||
await colorSpace.click();
|
||||
await driver.delayMs(1000);
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'ColorSpace_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number ColorSpacePhoto_001
|
||||
* @tc.name ColorSpacePhoto_001
|
||||
@ -304,17 +614,11 @@ export default function ActsAbilityTest() {
|
||||
it('ColorSpacePhoto_001', 23, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'ColorSpacePhoto_001 begin');
|
||||
let driver = Driver.create();
|
||||
await showColorSpace();
|
||||
let tip = getString($r('app.string.colorSpace'));
|
||||
await driver.assertComponentExist(ON.text(tip));
|
||||
let colorSpaceBtn = await driver.findComponent(ON.text(tip));
|
||||
await colorSpaceBtn.click();
|
||||
await driver.delayMs(1000);
|
||||
let colorSpace = getString($r('app.string.colorSpaceSRGB'));
|
||||
await driver.assertComponentExist(ON.text(colorSpace));
|
||||
let stack = await driver.findComponent(ON.text(colorSpace));
|
||||
await stack.click();
|
||||
await driver.delayMs(3000);
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('Ok'));
|
||||
let ok = await driver.findComponent(ON.id('Ok'));
|
||||
await ok.click();
|
||||
@ -401,9 +705,104 @@ export default function ActsAbilityTest() {
|
||||
await ok.click();
|
||||
await driver.delayMs(1000);
|
||||
// 保存当前编辑
|
||||
//await saveEdit();
|
||||
await saveEdit();
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'ColorSpacePhoto_004 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number DecodingPhoto_001
|
||||
* @tc.name DecodingPhoto_001
|
||||
* @tc.desc 点击修改hdr设置
|
||||
*/
|
||||
it('DecodingPhoto_001', 27, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE+ 'DecodingPhoto_001 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
// 进入编辑
|
||||
await enterEdit();
|
||||
await showColorSpace();
|
||||
let tip: string = getString($r('app.string.setting'));
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.text(tip));
|
||||
let setting = await driver.findComponent(ON.text(tip));
|
||||
await setting.click();
|
||||
await driver.delayMs(1000);
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'DecodingPhoto_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number Decoding_001
|
||||
* @tc.name Decoding_001
|
||||
* @tc.desc 点击修改hdr设置 >- 自动
|
||||
*/
|
||||
it('Decoding_001', 28, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Decoding_001 begin');
|
||||
let driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('auto'));
|
||||
let auto = await driver.findComponent(ON.id('auto'));
|
||||
await auto.click();
|
||||
await driver.delayMs(1000);
|
||||
// 保存当前编辑
|
||||
await saveEdit();
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Decoding_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number Decoding_002
|
||||
* @tc.name Decoding_002
|
||||
* @tc.desc 点击修改hdr设置 >- SDR
|
||||
*/
|
||||
it('Decoding_002', 29, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Decoding_002 begin');
|
||||
let driver = Driver.create();
|
||||
// 进入编辑
|
||||
await enterEdit();
|
||||
await showColorSpace();
|
||||
let tip: string = getString($r('app.string.setting'));
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.text(tip));
|
||||
let setting = await driver.findComponent(ON.text(tip));
|
||||
await setting.click();
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('sdr'));
|
||||
let sdr = await driver.findComponent(ON.id('sdr'));
|
||||
await sdr.click();
|
||||
await driver.delayMs(1000);
|
||||
// 保存当前编辑
|
||||
await saveEdit();
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Decoding_002 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* @tc.number Decoding_003
|
||||
* @tc.name Decoding_003
|
||||
* @tc.desc 点击修改hdr设置 >- HDR
|
||||
*/
|
||||
it('Decoding_003', 30, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + 'Decoding_003 begin');
|
||||
let driver = Driver.create() ;
|
||||
// 进入编辑
|
||||
await enterEdit();
|
||||
await showColorSpace();
|
||||
let tip: string = getString($r('app.string.setting'));
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.text(tip));
|
||||
let setting = await driver.findComponent(ON.text(tip));
|
||||
await setting.click();
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('hdr'));
|
||||
let hdr = await driver.findComponent(ON.id('hdr'));
|
||||
await hdr.click();
|
||||
await driver.delayMs(1000);
|
||||
// 保存当前编辑
|
||||
await saveEdit();
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, BUNDLE + "Decoding_003 end");
|
||||
})
|
||||
})
|
||||
}
|
@ -25,7 +25,11 @@
|
||||
|
||||
6.点击AudioTrack音轨列表选择框,可以选择音轨进行切换;
|
||||
|
||||
7.点击左上角退出箭头,退出应用。
|
||||
7.点击进入获取缩略图界面,可以获取视频的缩略图;
|
||||
|
||||
8.点击Subtitle开关列表选择框,可以切换外挂字幕是否显示;
|
||||
|
||||
9.点击左上角退出箭头,退出应用。
|
||||
|
||||
### 目录结构
|
||||
```
|
||||
@ -53,6 +57,7 @@ VideoPlay/src/main/ets/
|
||||
+ 视频跳转:在拖动滑动条时调用avPlayer.seek()
|
||||
+ 视频预下载:在prepared状态之前调用avPlayer.setMediaSource()
|
||||
+ 多音轨选择:调用avPlayer.getTrackDescription()获取音轨列表,调用avPlayer.selectTrack()选择音轨,调用avPlayer.deselectTrack()取消选择音轨。
|
||||
+ 外挂字幕显示切换:选择是否显示外挂字幕
|
||||
|
||||
### 相关权限
|
||||
|
||||
|
@ -21,7 +21,7 @@ export struct ExitVideo {
|
||||
build() {
|
||||
Row() {
|
||||
// 退出
|
||||
Image($r("app.media.ic_video_back"))
|
||||
Image($r('app.media.ic_video_back'))
|
||||
.id('Exit')
|
||||
.width($r('app.float.size_35'))
|
||||
.height($r('app.float.size_35'))
|
||||
|
@ -31,7 +31,7 @@ export struct SpeedDialog {
|
||||
Column() {
|
||||
Text($r('app.string.dialog_play_speed'))
|
||||
.fontSize($r('app.float.size_20'))
|
||||
.width("90%")
|
||||
.width('90%')
|
||||
.fontColor(Color.Black)
|
||||
.textAlign(TextAlign.Start)
|
||||
.margin({ top: $r('app.float.size_20'), bottom: $r('app.float.size_12') })
|
||||
@ -63,9 +63,9 @@ export struct SpeedDialog {
|
||||
.width('100%')
|
||||
}
|
||||
}
|
||||
.width("90%")
|
||||
.width('90%')
|
||||
}
|
||||
.width("100%")
|
||||
.width('100%')
|
||||
.height($r('app.float.size_48'))
|
||||
.onClick(() => {
|
||||
this.speedSelect = index;
|
||||
@ -92,7 +92,7 @@ export struct SpeedDialog {
|
||||
})
|
||||
})
|
||||
}
|
||||
.width("100%")
|
||||
.width('100%')
|
||||
.margin({
|
||||
top: $r('app.float.size_12')
|
||||
})
|
||||
@ -111,10 +111,10 @@ export struct SpeedDialog {
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.height($r('app.float.size_50'))
|
||||
.padding({ bottom: $r('app.float.size_5') })
|
||||
.width("100%")
|
||||
.width('100%')
|
||||
}
|
||||
.alignItems(HorizontalAlign.Center)
|
||||
.width("90%")
|
||||
.width('90%')
|
||||
.borderRadius($r('app.float.size_24'))
|
||||
.backgroundColor(Color.White)
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import { SpeedDialog } from '../components/SpeedDialog';
|
||||
|
||||
@Component
|
||||
export struct VideoOperate {
|
||||
@State videoList: Resource[] = [$r('app.string.video_res_1'), $r('app.string.video_res_2'), $r('app.string.video_res_3'), $r('app.string.video_res_4')];
|
||||
@State videoList: Resource[] = [$r('app.string.video_res_1'), $r('app.string.video_res_2'), $r('app.string.video_res_3'), $r('app.string.video_res_4'), $r('app.string.video_res_4')];
|
||||
@State speedSelect: number = 0; // 倍速选择
|
||||
@Link currentTime: number;
|
||||
@Link durationTime: number;
|
||||
@ -40,7 +40,7 @@ export struct VideoOperate {
|
||||
build() {
|
||||
Row() {
|
||||
Row() {
|
||||
Image(this.flag ? $r("app.media.ic_video_play") : $r("app.media.ic_video_pause")) // 暂停/播放
|
||||
Image(this.flag ? $r('app.media.ic_video_play') : $r('app.media.ic_video_pause')) // 暂停/播放
|
||||
.id('play')
|
||||
.width($r('app.float.size_40'))
|
||||
.height($r('app.float.size_40'))
|
||||
@ -72,7 +72,7 @@ export struct VideoOperate {
|
||||
.id('Slider')
|
||||
.blockColor(Color.White)
|
||||
.trackColor(Color.Gray)
|
||||
.selectedColor($r("app.color.slider_selected"))
|
||||
.selectedColor($r('app.color.slider_selected'))
|
||||
.showTips(false)
|
||||
.onChange((value: number, mode: SliderChangeMode) => {
|
||||
if (this.seekTime !== value) {
|
||||
|
@ -23,7 +23,7 @@ const VIDEOSELECT = 2; // 网络视频索引
|
||||
|
||||
@Component
|
||||
export struct VideoPanel {
|
||||
@State videoList: Resource[] = [$r('app.string.video_res_1'), $r('app.string.video_res_2'), $r('app.string.video_res_3'), $r('app.string.video_res_4')];
|
||||
@State videoList: Resource[] = [$r('app.string.video_res_1'), $r('app.string.video_res_2'), $r('app.string.video_res_3'), $r('app.string.video_res_4'), $r('app.string.video_res_5')];
|
||||
@State selectColor: boolean = true;
|
||||
@Link show: boolean;
|
||||
@Link videoSelect: number; // 当前选择项的索引
|
||||
@ -79,17 +79,17 @@ export struct VideoPanel {
|
||||
middle: { anchor: '__container__', align: HorizontalAlign.Center },
|
||||
center: { anchor: '__container__', align: VerticalAlign.Center }
|
||||
})
|
||||
.id("test1")
|
||||
.id('test1')
|
||||
Image($r('app.media.ic_video_list_down'))
|
||||
.width($r('app.float.size_30'))
|
||||
.height($r('app.float.size_20'))
|
||||
.alignRules({
|
||||
right: { anchor: '__container__', align: HorizontalAlign.End },
|
||||
center: { anchor: '__container__', align: VerticalAlign.Center }
|
||||
}).id("test2")
|
||||
}).id('test2')
|
||||
}
|
||||
.height($r('app.float.size_20'))
|
||||
.width("90%")
|
||||
.width('90%')
|
||||
.margin({ top: $r('app.float.size_20'), bottom: $r('app.float.size_20') })
|
||||
|
||||
List() {
|
||||
@ -114,12 +114,12 @@ export struct VideoPanel {
|
||||
.textAlign(TextAlign.Center)
|
||||
}
|
||||
}
|
||||
.width("90%")
|
||||
.width('90%')
|
||||
}
|
||||
.width("100%")
|
||||
.width('100%')
|
||||
}
|
||||
.backgroundColor(this.videoSelect === index ? $r('app.color.video_play_selected') : "")
|
||||
.width("100%")
|
||||
.backgroundColor(this.videoSelect === index ? $r('app.color.video_play_selected') : '')
|
||||
.width('100%')
|
||||
.height($r('app.float.size_64'))
|
||||
.onClick(async () => {
|
||||
this.videoSelect = index;
|
||||
@ -134,7 +134,7 @@ export struct VideoPanel {
|
||||
})
|
||||
})
|
||||
}
|
||||
.width("100%")
|
||||
.width('100%')
|
||||
}
|
||||
}
|
||||
.onClick(() => {
|
||||
@ -147,7 +147,7 @@ export struct VideoPanel {
|
||||
.type(PanelType.Foldable)
|
||||
.mode(PanelMode.Half)
|
||||
.dragBar(false)
|
||||
.halfHeight(330)
|
||||
.halfHeight(400)
|
||||
.width('100%')
|
||||
.mode(PanelMode.Half)
|
||||
.padding({ top: $r('app.float.size_5') })
|
||||
|
@ -23,6 +23,7 @@ import emitter from '@ohos.events.emitter';
|
||||
import { VideoPanel } from '../components/VideoPanel';
|
||||
import common from '@ohos.app.ability.common';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { router } from '@kit.ArkUI';
|
||||
|
||||
const PROPORTION = 0.99; // 占屏幕比例
|
||||
const SURFACEW = 0.9; // 表面宽比例
|
||||
@ -68,7 +69,9 @@ struct Index {
|
||||
@State windowWidth: number = 300;
|
||||
@State windowHeight: number = 300;
|
||||
@State audioTrack: number = 0;
|
||||
@State audioTrackValue: string = "";
|
||||
@State audioTrackValue: string = '';
|
||||
@State subtitleState: number = 0;
|
||||
@State subtitleStateValue: string = '';
|
||||
@StorageLink('videoName') videoName: Resource = $r('app.string.video_res_1');
|
||||
@StorageLink('videoIndex') videoIndex: number = 0; // 视频索引
|
||||
|
||||
@ -96,8 +99,13 @@ struct Index {
|
||||
getContext(this).resourceManager.getStringValue($r('app.string.default_track')).then((value: string) => {
|
||||
this.audioTrackValue = value;
|
||||
}).catch((error: BusinessError) => {
|
||||
console.error("getStringValue promise error is " + error);
|
||||
console.error('getStringValue promise error is ' + error);
|
||||
});
|
||||
getContext(this).resourceManager.getStringValue($r('app.string.subtitle_on')).then((value: string) => {
|
||||
this.subtitleStateValue = value
|
||||
}).catch((error: BusinessError) => {
|
||||
console.error('getStringValue promise error is ' + error)
|
||||
})
|
||||
}
|
||||
|
||||
aboutToDisappear() {
|
||||
@ -145,29 +153,50 @@ struct Index {
|
||||
}
|
||||
}
|
||||
|
||||
getSubtitleText(): string {
|
||||
if (this.avPlayManage) {
|
||||
return this.avPlayManage.getSubTitle()
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
@Builder
|
||||
CoverXComponent() {
|
||||
XComponent({ // 装载视频容器
|
||||
id: 'xComponent',
|
||||
type: 'surface',
|
||||
controller: this.xComponentController
|
||||
})
|
||||
.visibility(this.XComponentFlag ? Visibility.Visible : Visibility.Hidden)
|
||||
.onLoad(() => {
|
||||
this.surfaceId = this.xComponentController.getXComponentSurfaceId();
|
||||
this.avPlayManage.initPlayer(getContext(this) as common.UIAbilityContext, this.surfaceId, (avPlayer: media.AVPlayer) => {
|
||||
this.percent = avPlayer.width / avPlayer.height;
|
||||
this.setVideoWH();
|
||||
this.durationTime = this.avPlayManage.getDurationTime();
|
||||
setInterval(() => { // 更新当前时间
|
||||
if (!this.isSwiping) {
|
||||
this.currentTime = this.avPlayManage.getCurrentTime();
|
||||
}
|
||||
}, SET_INTERVAL);
|
||||
})
|
||||
Stack() {
|
||||
XComponent({ // 装载视频容器
|
||||
id: 'xComponent',
|
||||
type: 'surface',
|
||||
controller: this.xComponentController
|
||||
})
|
||||
.height(`${this.surfaceH}px`)
|
||||
.width(`${this.surfaceW}px`)
|
||||
.visibility(this.XComponentFlag ? Visibility.Visible : Visibility.Hidden)
|
||||
.onLoad(() => {
|
||||
this.surfaceId = this.xComponentController.getXComponentSurfaceId();
|
||||
this.avPlayManage.initPlayer(getContext(this) as common.UIAbilityContext, this.surfaceId, (avPlayer: media.AVPlayer) => {
|
||||
this.percent = avPlayer.width / avPlayer.height;
|
||||
this.setVideoWH();
|
||||
this.durationTime = this.avPlayManage.getDurationTime();
|
||||
setInterval(() => { // 更新当前时间
|
||||
if (!this.isSwiping) {
|
||||
this.currentTime = this.avPlayManage.getCurrentTime();
|
||||
}
|
||||
}, SET_INTERVAL);
|
||||
})
|
||||
})
|
||||
.height(`${this.surfaceH}px`)
|
||||
.width(`${this.surfaceW}px`)
|
||||
|
||||
// 字幕
|
||||
Column() {
|
||||
Text(this.getSubtitleText())
|
||||
.width(`${this.surfaceW}px`)
|
||||
.margin({ bottom: $r('app.float.size_5')})
|
||||
.fontColor(Color.White)
|
||||
.textAlign(TextAlign.Center)
|
||||
}
|
||||
.justifyContent(FlexAlign.End)
|
||||
.height(`${this.surfaceH}px`)
|
||||
.width(`${this.surfaceW}px`)
|
||||
}
|
||||
}
|
||||
|
||||
build() {
|
||||
@ -195,8 +224,8 @@ struct Index {
|
||||
Text(timeConvert(this.currentTime))
|
||||
.fontSize($r('app.float.size_24'))
|
||||
.opacity($r('app.float.size_1'))
|
||||
.fontColor($r("app.color.slider_selected"))
|
||||
Text("/" + timeConvert(this.durationTime))
|
||||
.fontColor($r('app.color.slider_selected'))
|
||||
Text('/' + timeConvert(this.durationTime))
|
||||
.fontSize($r('app.float.size_24'))
|
||||
.opacity($r('app.float.size_1'))
|
||||
.fontColor(Color.White)
|
||||
@ -212,23 +241,71 @@ struct Index {
|
||||
.width('100%')
|
||||
.justifyContent(FlexAlign.Start)
|
||||
|
||||
// 点击图标设置音频轨道
|
||||
Select(this.avPlayManage.arrList)
|
||||
.id('Select')
|
||||
.selected(this.audioTrack)
|
||||
.value('AudioTrack: ' + this.audioTrackValue)
|
||||
.font({ size: 16, weight: 400 })
|
||||
.visibility(this.avPlayManage.arrList.length > 2 ? Visibility.Visible : Visibility.Hidden)
|
||||
.fontColor(Color.White)
|
||||
.selectedOptionFont({ size: 16, weight: 400 })
|
||||
.optionFont({ size: 16, weight: 400 })
|
||||
.onSelect((index: number, value: string) => {
|
||||
if (this.audioTrack != index) {
|
||||
this.audioTrack = index;
|
||||
this.audioTrackValue = value;
|
||||
this.avPlayManage.setAudioTrack(index);
|
||||
Row() {
|
||||
Row() {
|
||||
Button() {
|
||||
Text($r('app.string.thumbnail_page'))
|
||||
.fontSize(30)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
}
|
||||
})
|
||||
.id('Thumbnail_1')
|
||||
.type(ButtonType.Capsule)
|
||||
.backgroundColor('#0D9FFB')
|
||||
.width('60%')
|
||||
.height('5%')
|
||||
.onClick(() => {
|
||||
router.pushUrl({
|
||||
url: 'pages/ThumbnailGet'
|
||||
}, router.RouterMode.Standard, (err) => {
|
||||
if (err) {
|
||||
console.error(`pushUrl failed, code is ${err.code}, message is ${err.message}`);
|
||||
return;
|
||||
}
|
||||
console.info('pushUrl success');
|
||||
});
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
|
||||
Row() {
|
||||
// 点击图标设置音频轨道
|
||||
Select(this.avPlayManage.arrList)
|
||||
.id('Select')
|
||||
.selected(this.audioTrack)
|
||||
.value('AudioTrack: ' + this.audioTrackValue)
|
||||
.font({ size: 16, weight: 400 })
|
||||
.visibility(this.avPlayManage.arrList.length > 2 ? Visibility.Visible : Visibility.Hidden)
|
||||
.fontColor(Color.White)
|
||||
.selectedOptionFont({ size: 16, weight: 400 })
|
||||
.optionFont({ size: 16, weight: 400 })
|
||||
.onSelect((index: number, value: string) => {
|
||||
if (this.audioTrack != index) {
|
||||
this.audioTrack = index;
|
||||
this.audioTrackValue = value;
|
||||
this.avPlayManage.setAudioTrack(index);
|
||||
}
|
||||
})
|
||||
|
||||
// 点击图标设置字幕开关
|
||||
Select(this.avPlayManage.arrSubTitleList)
|
||||
.id('Select2')
|
||||
.selected(this.subtitleState)
|
||||
.value('Subtitle: ' + this.subtitleStateValue)
|
||||
.font({ size: 16, weight: 400 })
|
||||
.visibility(this.avPlayManage.arrSubTitleList.length > 1 ? Visibility.Visible : Visibility.Hidden)
|
||||
.fontColor(Color.White)
|
||||
.selectedOptionFont({ size: 16, weight: 400 })
|
||||
.optionFont({ size: 16, weight: 400 })
|
||||
.optionWidth(30)
|
||||
.onSelect((index: number, value: string) => {
|
||||
if (this.subtitleState != index) {
|
||||
this.subtitleState = index;
|
||||
this.subtitleStateValue = value;
|
||||
this.avPlayManage.setSubtitleState(index);
|
||||
}
|
||||
})
|
||||
}
|
||||
Blank()
|
||||
|
||||
Column() {
|
||||
|
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Hunan OpenValley Digital Industry Development 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 image from '@ohos.multimedia.image';
|
||||
import fs from '@ohos.file.fs';
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct Index {
|
||||
@State pixelMap: PixelMap|undefined[] = [undefined, undefined, undefined, undefined, undefined]
|
||||
@State diffTime: number[] = [0, 0, 0, 0, 0, 0]
|
||||
|
||||
build() {
|
||||
Row() {
|
||||
Column() {
|
||||
Button() {
|
||||
Text($r('app.string.get_thumbnail'))
|
||||
.fontSize(30)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
}
|
||||
.id('Thumbnail_2')
|
||||
.type(ButtonType.Capsule)
|
||||
.backgroundColor('#0D9FFB')
|
||||
.width('60%')
|
||||
.height('5%')
|
||||
.onClick(() => {
|
||||
this.fetchFrameAndMeta()
|
||||
})
|
||||
|
||||
Row() {
|
||||
Image(this.pixelMap[0]).width(130).height(200).objectFit(ImageFit.Fill).margin({ top: 20 })
|
||||
Image(this.pixelMap[1]).width(130).height(200).objectFit(ImageFit.Fill).margin({ top: 20 })
|
||||
Image(this.pixelMap[2]).width(130).height(200).objectFit(ImageFit.Fill).margin({ top: 20 })
|
||||
}
|
||||
|
||||
Row() {
|
||||
Text(this.diffTime[0].toString()).width(130).height(30)
|
||||
Text(this.diffTime[1].toString()).width(130).height(30)
|
||||
Text(this.diffTime[2].toString()).width(130).height(30)
|
||||
}
|
||||
|
||||
Row() {
|
||||
Image(this.pixelMap[3]).width(130).height(200).objectFit(ImageFit.Fill).margin({ top: 20 })
|
||||
Image(this.pixelMap[4]).width(130).height(200).objectFit(ImageFit.Fill).margin({ top: 20 })
|
||||
Image(this.pixelMap[5]).width(130).height(200).objectFit(ImageFit.Fill).margin({ top: 20 })
|
||||
}
|
||||
|
||||
Row() {
|
||||
Text(this.diffTime[3].toString()).width(130).height(30)
|
||||
Text(this.diffTime[4].toString()).width(130).height(30)
|
||||
Text(this.diffTime[5].toString()).width(130).height(30)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.height('100%')
|
||||
}
|
||||
|
||||
async fetchFrameAndMeta() {
|
||||
let avFileDescriptor = await globalThis.getContext().resourceManager.getRawFd('test.mp4')
|
||||
if (canIUse('SystemCapability.Multimedia.Media.AVMetadataExtractor')) {
|
||||
let avMetadataExtractor = await media.createAVMetadataExtractor()
|
||||
avMetadataExtractor.fdSrc = avFileDescriptor
|
||||
let metadata = await avMetadataExtractor.fetchMetadata()
|
||||
console.info(`winddraw rotation ${metadata.videoOrientation}`)
|
||||
let duration = Number(metadata.duration)
|
||||
this.diffTime[0] = 0
|
||||
for (let i = 0; i < 4; i++) {
|
||||
this.diffTime[i + 1] = this.diffTime[i] + duration / 5 * 1000
|
||||
}
|
||||
this.diffTime[5] = duration * 1000
|
||||
avMetadataExtractor.release()
|
||||
}
|
||||
|
||||
if (canIUse('SystemCapability.Multimedia.Media.AVImageGenerator')) {
|
||||
let queryOption = media.AVImageQueryOptions.AV_IMAGE_QUERY_CLOSEST_SYNC
|
||||
for (let i = 0; i < 6; i++) {
|
||||
let avImageGenerator: media.AVImageGenerator = await media.createAVImageGenerator()
|
||||
avImageGenerator.fdSrc = avFileDescriptor
|
||||
this.pixelMap[i] = await avImageGenerator.fetchFrameByTime(this.diffTime[i], queryOption, {})
|
||||
avImageGenerator.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
savePixelMapData(pixelMap: PixelMap) {
|
||||
pixelMap.getImageInfo().then((imageInfo: image.ImageInfo) => {
|
||||
if (imageInfo == undefined) {
|
||||
console.error('Failed to obtain the image pixel map information');
|
||||
}
|
||||
const readBuffer: ArrayBuffer = new ArrayBuffer(imageInfo.size.height * imageInfo.size.width * 4)
|
||||
pixelMap.readPixelsToBuffer(readBuffer).then(() => {
|
||||
let filePath = globalThis.getContext().filesDir + '/winddraw.dat';
|
||||
let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
|
||||
fs.writeSync(file.fd, readBuffer)
|
||||
fs.closeSync(file)
|
||||
}).catch(() => {
|
||||
console.error('Failed to read image pixel data.');
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@ -39,10 +39,16 @@ export default class AvPlayManager {
|
||||
private fileDescriptor: resourceManager.RawFileDescriptor | null = null;
|
||||
private videoSrc: string | null = null;
|
||||
private fileSrc: string | null = null;
|
||||
private default_track_value: string = "";
|
||||
private track_name_value: string = "";
|
||||
private default_track_value: string = '';
|
||||
private track_name_value: string = '';
|
||||
private currentAudioTrackValue: number = 0 ;
|
||||
public arrList: Array<SelectOption> = [];
|
||||
// 字幕
|
||||
public arrSubTitleList: Array<SelectOption> = []
|
||||
private currentSubtitleStateValue: number = 0
|
||||
private text: string | null = ''
|
||||
private startTime: number | null = 0
|
||||
private duration: number | null = 0
|
||||
|
||||
/**
|
||||
* 初始化视频
|
||||
@ -60,19 +66,23 @@ export default class AvPlayManager {
|
||||
Logger.info(this.tag, 'initPlayer videoPlay setAVPlayerCallback');
|
||||
this.mgr = ctx.resourceManager;
|
||||
Logger.info(this.tag, 'initPlayer videoPlay this.mgr');
|
||||
this.fileDescriptor = await this.mgr.getRawFd('test1.mp4');
|
||||
this.videoSrc = 'test1.mp4'
|
||||
this.fileDescriptor = await this.mgr.getRawFd(this.videoSrc);
|
||||
Logger.info(this.tag, `initPlayer videoPlay fileDescriptor = ${JSON.stringify(this.fileDescriptor)}`);
|
||||
this.avPlayer.fdSrc = this.fileDescriptor;
|
||||
this.addSubtitleFdSrc()
|
||||
this.mgr.getStringValue($r('app.string.default_track')).then((value: string) => {
|
||||
this.default_track_value = value;
|
||||
}).catch((error: BusinessError) => {
|
||||
console.error("getStringValue promise error is " + error);
|
||||
console.error('getStringValue promise error is ' + error);
|
||||
});
|
||||
this.mgr.getStringValue($r('app.string.track_name')).then((value: string) => {
|
||||
this.track_name_value = value;
|
||||
}).catch((error: BusinessError) => {
|
||||
console.error("getStringValue promise error is " + error);
|
||||
console.error('getStringValue promise error is ' + error);
|
||||
});
|
||||
// 字幕相关
|
||||
await this.getSubtitleStates()
|
||||
} catch (err) {
|
||||
Logger.error(this.tag, `initPlayer initPlayer err:${JSON.stringify(err)}`);
|
||||
}
|
||||
@ -99,7 +109,7 @@ export default class AvPlayManager {
|
||||
});
|
||||
// 状态机变化回调函数
|
||||
this.avPlayer.on('stateChange', async (state, reason) => {
|
||||
|
||||
Logger.info(this.tag, 'stateChange is called >> state = ' + state)
|
||||
if (this.avPlayer == null) {
|
||||
Logger.info(this.tag, 'avPlayer has not init');
|
||||
return;
|
||||
@ -162,6 +172,17 @@ export default class AvPlayManager {
|
||||
this.currentTime = time;
|
||||
Logger.info(this.tag, `setAVPlayerCallback timeUpdate success,and new time is = ${this.currentTime}`);
|
||||
});
|
||||
// 字幕回调函数
|
||||
this.avPlayer.on('subtitleUpdate', (info: media.SubtitleInfo) => {
|
||||
if (!!info) {
|
||||
Logger.info(this.tag, `subtitleUpdate info is called text=${info.text} startTime=${info.startTime} duration=${info.duration}`)
|
||||
this.text = (!info.text) ? '' : info.text
|
||||
this.startTime = (!info.startTime) ? 0 : info.startTime
|
||||
this.duration = (!info.duration) ? 0 : info.duration
|
||||
} else {
|
||||
Logger.info(this.tag, 'subtitleUpdate info is null')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -285,6 +306,7 @@ export default class AvPlayManager {
|
||||
return;
|
||||
}
|
||||
this.avPlayer.reset();
|
||||
this.clearSubtitle()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -292,7 +314,7 @@ export default class AvPlayManager {
|
||||
*/
|
||||
async preDownload(url: string): Promise<void> {
|
||||
if (this.avPlayer) {
|
||||
let mediaSource : media.MediaSource = media.createMediaSourceWithUrl(url, {"aa" : "bb", "cc" : "dd"});
|
||||
let mediaSource : media.MediaSource = media.createMediaSourceWithUrl(url, {'aa' : 'bb', 'cc' : 'dd'});
|
||||
let playbackStrategy : media.PlaybackStrategy = {preferredWidth: 1, preferredHeight: 2, preferredBufferDuration: 3, preferredHdr: false};
|
||||
this.avPlayer.setMediaSource(mediaSource, playbackStrategy)
|
||||
}
|
||||
@ -401,15 +423,17 @@ export default class AvPlayManager {
|
||||
this.fileSrc = 'https:\/\/vd3.bdstatic.com\/mda-pdc2kmwtd2vxhiy4\/cae_h264\/1681502407203843413\/mda-pdc2kmwtd2vxhiy4.mp4';
|
||||
} else if (this.videoSrc === 'pre_download_network.mp4') {
|
||||
this.fileSrc = 'https:\/\/vd3.bdstatic.com\/mda-nmh2004d24kf4bjh\/hd/h264\/1671326683061787710\/mda-nmh2004d24kf4bjh.mp4';
|
||||
} else if (this.videoSrc === 'pre_download_dash.mpd') {
|
||||
this.fileSrc = 'https:\/\/hwposter-inland.hwcloudtest.cn\/AiMaxEngine\/DASH_LOCAL\/DASH_SDR_H264_LC\/DASH_SDR_H264_LC.mpd';
|
||||
} else {
|
||||
this.fileSrc = this.videoSrc;
|
||||
}
|
||||
if (this.fileSrc) {
|
||||
let regex = new RegExp("^(http|https)", "i");
|
||||
let regex = /^(http|https)/i;
|
||||
let bool = regex.test(this.fileSrc);
|
||||
if (bool) {
|
||||
Logger.info(this.tag, `avPlayerChoose avPlayerChoose fileDescriptor = ${JSON.stringify(this.fileDescriptor)}`);
|
||||
if (this.videoSrc === 'pre_download_network.mp4') {
|
||||
if (this.videoSrc === 'pre_download_network.mp4' || this.videoSrc === 'pre_download_dash.mpd') {
|
||||
this.preDownload(this.fileSrc);
|
||||
} else {
|
||||
this.avPlayer.url = this.fileSrc;
|
||||
@ -419,6 +443,8 @@ export default class AvPlayManager {
|
||||
this.fileDescriptor = await this.mgr.getRawFd(this.fileSrc);
|
||||
Logger.info(this.tag, `avPlayerChoose avPlayerChoose fileDescriptor = ${JSON.stringify(this.fileDescriptor)}`);
|
||||
this.avPlayer.fdSrc = this.fileDescriptor;
|
||||
// 字幕
|
||||
this.addSubtitleFdSrc()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -427,4 +453,73 @@ export default class AvPlayManager {
|
||||
this.videoReset();
|
||||
}
|
||||
}
|
||||
|
||||
// 字幕相关
|
||||
async getSubtitleStates(): Promise<void> {
|
||||
let selectOptions: SelectOption[] = []
|
||||
if (this.mgr) {
|
||||
this.mgr.getStringValue($r('app.string.subtitle_on')).then((v: string) => {
|
||||
selectOptions.push({ value: v})
|
||||
}).catch((error: BusinessError) => {
|
||||
Logger.error('subtitle getStringValue promise error is ' + error)
|
||||
})
|
||||
this.mgr.getStringValue($r('app.string.subtitle_off')).then((v: string) => {
|
||||
selectOptions.push({ value: v})
|
||||
}).catch((e: BusinessError) => {
|
||||
Logger.error('subtitle getStringValue promise error is ' + e)
|
||||
})
|
||||
this.arrSubTitleList = selectOptions
|
||||
}
|
||||
Logger.info(this.tag, 'subtitle getSubtitleStates end:' + `${JSON.stringify(this.arrSubTitleList.length)}`)
|
||||
}
|
||||
|
||||
async setSubtitleState(subtitleState: number): Promise<void> {
|
||||
Logger.info(this.tag, 'subtitle setSubtitleState value is:' + subtitleState)
|
||||
this.currentSubtitleStateValue = subtitleState
|
||||
}
|
||||
|
||||
getSubTitle(): string {
|
||||
if (this.currentSubtitleStateValue == 0 && !!this.startTime && !!this.duration && !!this.text) {
|
||||
if (this.startTime > 0 && this.duration > 0 && this.currentTime > 0) {
|
||||
let dis = this.currentTime - this.startTime
|
||||
if (0 <= dis && dis <= this.duration) {
|
||||
return this.text
|
||||
} else {
|
||||
Logger.info(this.tag, 'getSubTitle there is no subtitle at current time')
|
||||
}
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
clearSubtitle(): void {
|
||||
this.text = ''
|
||||
this.startTime = 0
|
||||
this.duration = 0
|
||||
Logger.info(this.tag, 'subtitle clearSubtitle')
|
||||
}
|
||||
|
||||
async addSubtitleFdSrc(): Promise<void> {
|
||||
if (!this.videoSrc) {
|
||||
Logger.info(this.tag, 'subtitle addSubtitleFdSrc videoSrc is null')
|
||||
return
|
||||
}
|
||||
if (!this.mgr) {
|
||||
Logger.info(this.tag, 'subtitle addSubtitleFdSrc this.mgr is null')
|
||||
return
|
||||
}
|
||||
let subtitleSource = this.videoSrc.split('.')[0] + '.srt'
|
||||
Logger.info(this.tag, 'subtitle addSubtitleFdSrc ' + subtitleSource)
|
||||
this.fileDescriptor = await this.mgr.getRawFd(subtitleSource)
|
||||
if (!this.fileDescriptor) {
|
||||
Logger.info(this.tag, 'subtitle subtitleSource maybe not exists')
|
||||
return
|
||||
}
|
||||
if (!this.avPlayer) {
|
||||
Logger.info(this.tag, 'subtitle addSubtitleFdSrc avPlayer has not init')
|
||||
return
|
||||
}
|
||||
Logger.info(this.tag, `subtitle fd=${this.fileDescriptor.fd}, offset=${this.fileDescriptor.offset}, length=${this.fileDescriptor.length}`)
|
||||
this.avPlayer.addSubtitleFromFd(this.fileDescriptor.fd, this.fileDescriptor.offset, this.fileDescriptor.length)
|
||||
}
|
||||
}
|
@ -28,6 +28,10 @@
|
||||
"name": "video_res_4",
|
||||
"value": "pre_download_network.mp4"
|
||||
},
|
||||
{
|
||||
"name": "video_res_5",
|
||||
"value": "pre_download_dash.mpd"
|
||||
},
|
||||
{
|
||||
"name": "video_speed_1_0X",
|
||||
"value": "1.0X"
|
||||
@ -71,6 +75,22 @@
|
||||
{
|
||||
"name": "track_name_1",
|
||||
"value": "轨道:1"
|
||||
},
|
||||
{
|
||||
"name": "thumbnail_page",
|
||||
"value": "进入获取缩略图页面"
|
||||
},
|
||||
{
|
||||
"name": "get_thumbnail",
|
||||
"value": "获取缩略图(test.mp4)"
|
||||
},
|
||||
{
|
||||
"name": "subtitle_on",
|
||||
"value": "开"
|
||||
},
|
||||
{
|
||||
"name": "subtitle_off",
|
||||
"value": "关"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"src": [
|
||||
"pages/Index"
|
||||
"pages/Index",
|
||||
"pages/ThumbnailGet"
|
||||
]
|
||||
}
|
||||
}
|
@ -28,6 +28,10 @@
|
||||
"name": "video_res_4",
|
||||
"value": "pre_download_network.mp4"
|
||||
},
|
||||
{
|
||||
"name": "video_res_5",
|
||||
"value": "pre_download_dash.mpd"
|
||||
},
|
||||
{
|
||||
"name": "video_speed_1_0X",
|
||||
"value": "1.0X"
|
||||
@ -71,6 +75,14 @@
|
||||
{
|
||||
"name": "track_name_1",
|
||||
"value": "track:1"
|
||||
},
|
||||
{
|
||||
"name": "thumbnail_page",
|
||||
"value": "进入获取缩略图页面"
|
||||
},
|
||||
{
|
||||
"name": "get_thumbnail",
|
||||
"value": "获取缩略图(test.mp4)"
|
||||
}
|
||||
]
|
||||
}
|
BIN
code/BasicFeature/Media/VideoPlay/entry/src/main/resources/rawfile/test.mp4
(Stored with Git LFS)
Normal file
@ -0,0 +1,123 @@
|
||||
1
|
||||
00:00:00,470 --> 00:00:07,480
|
||||
(在空心地球的某处)
|
||||
|
||||
2
|
||||
00:00:09,460 --> 00:00:12,670
|
||||
在大部分人类文明中
|
||||
|
||||
3
|
||||
00:00:13,840 --> 00:00:15,550
|
||||
我们都认为人类是地球上的优势物种
|
||||
|
||||
4
|
||||
00:00:15,920 --> 00:00:16,890
|
||||
更相信生命只能存在于地球表面
|
||||
|
||||
5
|
||||
00:00:16,890 --> 00:00:18,970
|
||||
但在某个时刻,大家一定会想
|
||||
|
||||
6
|
||||
00:00:19,140 --> 00:00:20,220
|
||||
(君主计划金刚研究负责人 艾琳安德鲁博士)
|
||||
|
||||
7
|
||||
00:00:21,310 --> 00:00:23,140
|
||||
我们的认知还有哪些错误?
|
||||
|
||||
8
|
||||
00:00:20,400 --> 00:00:24,690
|
||||
我们目前只探勘了空心地球 不到百分之五的区域
|
||||
|
||||
9
|
||||
00:00:25,280 --> 00:00:30,160
|
||||
但地表上下 生态系统的关系紧密超乎想像
|
||||
|
||||
10
|
||||
00:00:30,910 --> 00:00:34,160
|
||||
这不是两个不同世界,而是一体的
|
||||
|
||||
11
|
||||
00:00:34,330 --> 00:00:35,580
|
||||
(发现空心地球新入口)
|
||||
|
||||
12
|
||||
00:00:37,540 --> 00:00:39,130
|
||||
金刚已在空心地球定居
|
||||
|
||||
13
|
||||
00:00:39,210 --> 00:00:39,960
|
||||
(金刚移居新栖地)
|
||||
|
||||
14
|
||||
00:00:40,040 --> 00:00:42,210
|
||||
所以,这位大家伙喜欢它的新家
|
||||
|
||||
15
|
||||
00:00:42,500 --> 00:00:43,550
|
||||
应该没错
|
||||
|
||||
16
|
||||
00:00:44,010 --> 00:00:45,970
|
||||
它是高度社会化的物种
|
||||
|
||||
17
|
||||
00:00:46,550 --> 00:00:49,680
|
||||
金刚如此孤单并不正常
|
||||
|
||||
18
|
||||
00:00:52,470 --> 00:00:54,020
|
||||
它是同类中的最后一只
|
||||
|
||||
19
|
||||
00:00:54,180 --> 00:00:57,690
|
||||
它每天都在寻找永远找不到的家人
|
||||
|
||||
20
|
||||
00:00:59,730 --> 00:01:00,900
|
||||
那哥斯拉呢?
|
||||
|
||||
21
|
||||
00:01:00,980 --> 00:01:01,650
|
||||
(哥斯拉领地)
|
||||
|
||||
22
|
||||
00:01:01,730 --> 00:01:03,690
|
||||
哥斯拉在地表,金刚在地心
|
||||
|
||||
23
|
||||
00:01:03,780 --> 00:01:04,320
|
||||
(金刚领地)
|
||||
|
||||
24
|
||||
00:01:04,400 --> 00:01:06,700
|
||||
只要它们不冒险进入对方领地
|
||||
|
||||
25
|
||||
00:01:06,820 --> 00:01:08,410
|
||||
就没什么好担心的
|
||||
|
||||
26
|
||||
00:01:08,910 --> 00:01:11,780
|
||||
我们很幸运有哥斯拉扞卫人类世界
|
||||
|
||||
27
|
||||
00:01:11,950 --> 00:01:12,780
|
||||
(罗马,泰坦24号)
|
||||
|
||||
28
|
||||
00:01:17,870 --> 00:01:20,330
|
||||
(意大利,罗马)
|
||||
|
||||
29
|
||||
00:01:22,860 --> 00:01:25,570
|
||||
部分论点批评君主计划 花费纳税人数十亿美元
|
||||
|
||||
30
|
||||
00:01:26,780 --> 00:01:28,120
|
||||
(君主一号基地,金刚观测站)
|
||||
|
||||
31
|
||||
00:01:29,180 --> 00:01:49,120
|
||||
结束
|
@ -44,6 +44,10 @@
|
||||
"name": "video_res_4",
|
||||
"value": "pre_download_network.mp4"
|
||||
},
|
||||
{
|
||||
"name": "video_res_5",
|
||||
"value": "pre_download_dash.mpd"
|
||||
},
|
||||
{
|
||||
"name": "video_speed_2_0X",
|
||||
"value": "2.0X"
|
||||
@ -71,6 +75,22 @@
|
||||
{
|
||||
"name": "track_name_1",
|
||||
"value": "轨道:1"
|
||||
},
|
||||
{
|
||||
"name": "thumbnail_page",
|
||||
"value": "进入获取缩略图页面"
|
||||
},
|
||||
{
|
||||
"name": "get_thumbnail",
|
||||
"value": "获取缩略图(test.mp4)"
|
||||
},
|
||||
{
|
||||
"name": "subtitle_on",
|
||||
"value": "开"
|
||||
},
|
||||
{
|
||||
"name": "subtitle_off",
|
||||
"value": "关"
|
||||
}
|
||||
]
|
||||
}
|
@ -17,6 +17,7 @@ import { Driver, ON } from '@ohos.UiTest';
|
||||
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
|
||||
import { getString } from '../utils/ResourceUtil';
|
||||
import hilog from '@ohos.hilog';
|
||||
import { router } from '@kit.ArkUI';
|
||||
|
||||
const TAG = '[Sample_VideoPlay]';
|
||||
const DOMAIN = 0xF811;
|
||||
@ -65,6 +66,34 @@ export default function abilityTest() {
|
||||
console.info(TAG, 'StartAbility_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* 点击外挂字幕-关
|
||||
*/
|
||||
it('Subtitle_001', 0, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, 'Subtitle_001 begin')
|
||||
|
||||
// 拉起操作界面
|
||||
await driver.delayMs(1000)
|
||||
await driver.assertComponentExist(ON.id('Video'))
|
||||
let stack = await driver.findComponent(ON.id('Video'))
|
||||
await driver.delayMs(1000)
|
||||
await stack.click()
|
||||
|
||||
// 点击外挂字幕
|
||||
await driver.assertComponentExist(ON.id('Select2'))
|
||||
let btnSubtitle = await driver.findComponent(ON.id('Select2'))
|
||||
await btnSubtitle.click()
|
||||
|
||||
// 点击关
|
||||
const str = await getString($r('app.string.subtitle_off'))
|
||||
await driver.assertComponentExist(ON.text(str))
|
||||
let button = await driver.findComponent(ON.text(str))
|
||||
await button.click()
|
||||
await driver.delayMs(2000)
|
||||
done()
|
||||
hilog.info(DOMAIN, TAG, 'Subtitle_001 end')
|
||||
})
|
||||
|
||||
/**
|
||||
* 点击多音轨轨道:1
|
||||
*/
|
||||
@ -93,6 +122,31 @@ export default function abilityTest() {
|
||||
hilog.info(DOMAIN, TAG, 'Select_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* 切换pre_download_dash.mpd视频
|
||||
*/
|
||||
it('Choose_003', 0, async (done: Function) => {
|
||||
// 拉起操作界面
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.id('Video'));
|
||||
let stack = await driver.findComponent(ON.id('Video'));
|
||||
await driver.delayMs(1000);
|
||||
await stack.click();
|
||||
// 点击视频列表
|
||||
await driver.assertComponentExist(ON.id('Choose'));
|
||||
let btnPlay = await driver.findComponent(ON.id('Choose'));
|
||||
await btnPlay.click();
|
||||
|
||||
// 点击pre_download_dash.mpd
|
||||
const str = await getString($r('app.string.video_res_5'));
|
||||
await driver.assertComponentExist(ON.text(str));
|
||||
let button = await driver.findComponent(ON.text(str));
|
||||
await button.click();
|
||||
await driver.delayMs(2000);
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, 'Choose_003 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* 切换pre_download_network.mp4视频
|
||||
*/
|
||||
@ -115,7 +169,7 @@ export default function abilityTest() {
|
||||
await driver.assertComponentExist(ON.text(str));
|
||||
let button = await driver.findComponent(ON.text(str));
|
||||
await button.click();
|
||||
await driver.delayMs(5000);
|
||||
await driver.delayMs(2000);
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, 'Choose_002 end');
|
||||
})
|
||||
@ -237,6 +291,37 @@ export default function abilityTest() {
|
||||
hilog.info(DOMAIN, TAG, 'PlayPause_002 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* 点击获取缩略图
|
||||
*/
|
||||
it('Thumbnail_001', 0, async (done: Function) => {
|
||||
hilog.info(DOMAIN, TAG, 'Thumbnail_001 begin');
|
||||
|
||||
// 拉起操作界面
|
||||
await driver.delayMs(8000);
|
||||
await driver.assertComponentExist(ON.id('Video'));
|
||||
let stack = await driver.findComponent(ON.id('Video'));
|
||||
await stack.click();
|
||||
|
||||
// 点击进入获取缩略图界面
|
||||
await driver.assertComponentExist(ON.id('Thumbnail_1'));
|
||||
let thumbnailButton1 = await driver.findComponent(ON.id('Thumbnail_1'));
|
||||
await thumbnailButton1.click();
|
||||
|
||||
// 点击获取缩略图
|
||||
await driver.assertComponentExist(ON.id('Thumbnail_2'));
|
||||
let thumbnailButton2 = await driver.findComponent(ON.id('Thumbnail_2'));
|
||||
await thumbnailButton2.click();
|
||||
await driver.delayMs(2000);
|
||||
|
||||
// 退回到视频播放页面
|
||||
router.back(1)
|
||||
await driver.delayMs(1000);
|
||||
await stack.click();
|
||||
done();
|
||||
hilog.info(DOMAIN, TAG, 'Thumbnail_001 end');
|
||||
})
|
||||
|
||||
/**
|
||||
* 退出测试
|
||||
*/
|
||||
|
@ -32,6 +32,10 @@
|
||||
"name": "video_res_4",
|
||||
"value": "pre_download_network.mp4"
|
||||
},
|
||||
{
|
||||
"name": "video_res_5",
|
||||
"value": "pre_download_dash.mpd"
|
||||
},
|
||||
{
|
||||
"name": "video_speed_1_0X",
|
||||
"value": "1.0X"
|
||||
@ -75,6 +79,10 @@
|
||||
{
|
||||
"name": "track_name_1",
|
||||
"value": "轨道:1"
|
||||
},
|
||||
{
|
||||
"name": "subtitle_off",
|
||||
"value": "关"
|
||||
}
|
||||
]
|
||||
}
|
@ -2,13 +2,15 @@
|
||||
|
||||
## 用例表
|
||||
|
||||
|测试功能|预置条件|输入|预期输出|测试结果|
|
||||
|--------------------------------|--------------------------------|--------------------------------|--------------------------------|--------------------------------|
|
||||
|拉起应用|设备正常运行| 点击应用 |成功拉起应用|Pass|
|
||||
|拉起操作面板|设备正常运行|点击视频界面|成功拉起操作面板|Pass|
|
||||
|暂停、播放|在操作界面 | 1、点击**暂停**按钮<br>2、点击**继续**按钮 |1、视频暂停<br>2、视频播放|Pass|
|
||||
|切换倍速|在操作界面 | 点击倍速,选择2.0X | 倍速变为2.0X |Pass|
|
||||
| 测试功能 |预置条件|输入|预期输出|测试结果|
|
||||
|---|--------------------------------|------------------------------|------------------------|--------------------------------|
|
||||
| 拉起应用 |设备正常运行| 点击应用 |成功拉起应用|Pass|
|
||||
| 拉起操作面板 |设备正常运行|点击视频界面|成功拉起操作面板|Pass|
|
||||
| 暂停、播放 |在操作界面 | 1、点击**暂停**按钮<br>2、点击**继续**按钮 |1、视频暂停<br>2、视频播放|Pass|
|
||||
| 切换倍速 |在操作界面 | 点击倍速,选择2.0X | 倍速变为2.0X |Pass|
|
||||
| 切换视频 | 在操作界面 | 点击视频名,选择test2视频 | 切换到test2视频 |Pass|
|
||||
|视频跳转| 在操作界面 | 滑动滑动条 | 滑动后视频跳转到相应位置 |Pass|
|
||||
| 视频跳转 | 在操作界面 | 滑动滑动条 | 滑动后视频跳转到相应位置 |Pass|
|
||||
| 退出应用 | 在操作界面 | 点击左上角退出按钮 | 成功退出应用|Pass|
|
||||
| 切换音轨 | 在操作界面 | 点击多音轨列表选项 | 成功切换音轨|Pass|
|
||||
| 切换音轨 | 在操作界面 | 点击多音轨列表选项 | 成功切换音轨|Pass|
|
||||
| 获取缩略图 | 在操作界面 | 点击进入获取缩略图界面并点击获取缩略图 | 成功获取缩略图|Pass|
|
||||
| 切换外挂字幕开关 | 在操作界面 | 点击外挂字幕列表选项 | 成功切换外挂字幕开关 |Pass|
|
||||
|
@ -95,7 +95,7 @@ export struct AddInformationView {
|
||||
builder: AgePickDialog({ sure: (age: number) => {
|
||||
this.information.age = age;
|
||||
} }),
|
||||
customStyle: true,
|
||||
customStyle: false,
|
||||
alignment: DialogAlignment.Bottom
|
||||
})
|
||||
@State flag: string = '';
|
||||
|
@ -14,7 +14,23 @@
|
||||
1. 进入页面默认开始2016动画,点击**请选择**进行选择动画资源;
|
||||
2. 上面部分播放暂停是对两个动画进行控制,下面部分播放暂停功能是对grunt动画控制;
|
||||
3. 点击销毁动画功能之后需要重新选择动画资源才可以进行其余功能操作。
|
||||
|
||||
4. 点击下面部分左侧第一个播放/暂停,右侧出现**通过name实现对grunt动画展示效果**,点击右侧播放/暂停,grunt动画会暂停播放,再次点击播放/暂停,grunt动画恢复播放。
|
||||
5. 点击停止,grunt动画停止播放,点击播放/暂停或者开始播放,grunt动画恢复播放。
|
||||
6. 点击暂停,grunt动画暂停播放,点击播放/暂停或者开始播放,grunt动画恢复播放。
|
||||
7. 点击二倍速,设置grunt动画播放速度为二倍速。
|
||||
8. 点击逆序播放,设置grunt动画播放方向为反向播放。
|
||||
9. 点击下面部分左侧第二个播放/暂停,右侧出现**通过this实现对grunt动画展示效果**,点击五倍速,设置grunt动画播放速度为五倍速,可以明显看到播放速度较之上面的二倍速更快。其余按钮操作步骤同上述第4,5,6,7,8条一致。
|
||||
10. 点击左侧循环,点击指定片段循环(20,30)帧,限定grunt动画资源播放时的整体帧范围为从20帧开始到30帧结束。
|
||||
11. 点击playSegments片段循环,设置grunt动画仅播放指定范围的20-30帧动画。
|
||||
12. 点击重置播放片段,选择即时生效播放或延迟到下轮循环播放再生效,可以使grunt动画重新从第一帧开始播放完整动画。
|
||||
13. 点击去浮点显示整数,弹出文本提示框,提示框文本内容为:使用AE原始帧率播放动画。
|
||||
14. 点击播放位置,点击动画停止,选择250帧时停止,控制grunt动画画面停止在250帧,点击动画播放,选择从250帧开始播放,控制grunt动画从250帧开始播放。
|
||||
15. 点击动画停止,选择5000ms时停止,控制grunt动画画面停止在5000ms,点击动画播放,选择从12000ms开始播放,控制grunt动画从12000ms开始播放。
|
||||
16. 点击刷新动画布局,可以刷新动画布局。
|
||||
17. 点击销毁grunt动画,通过动画名称控制, grunt动画被销毁。
|
||||
18. 点击下面部分左侧添加侦听事件,点击右侧添加侦听事件按钮,loopComplete事件完成后会触发指定回调函数,弹出文本提示框,提示框文本内容为:grunt loopComplete 。
|
||||
19. 点击删除侦听事件,grunt动画停止播放,点击删除所有侦听事件,grunt动画恢复播放。
|
||||
20. 点击触发所有已设置的回调,grunt动画暂停播放,点击删除所有侦听事件,grunt动画恢复播放。
|
||||
### 工程目录
|
||||
```
|
||||
entry/src/main/ets/
|
||||
|
@ -29,6 +29,7 @@
|
||||
*/
|
||||
|
||||
import lottie from '@ohos/lottie'
|
||||
import promptAction from '@ohos.promptAction'
|
||||
import Logger from '../util/Logger'
|
||||
|
||||
const TAG: string = 'Lottie'
|
||||
@ -326,6 +327,7 @@ struct Lottie {
|
||||
}
|
||||
.width('100%')
|
||||
.justifyContent(FlexAlign.Start)
|
||||
.margin({ top: 10 })
|
||||
}
|
||||
.justifyContent(FlexAlign.SpaceAround)
|
||||
.backgroundColor('#FFFFFF')
|
||||
@ -690,6 +692,9 @@ struct Lottie {
|
||||
.onClick(() => {
|
||||
if (this.animateItem !== null) {
|
||||
this.animateItem.setSubframe(false)
|
||||
promptAction.showToast({
|
||||
message: $r('app.string.use_ae_fps')
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
@ -842,6 +847,9 @@ struct Lottie {
|
||||
.onClick(() => {
|
||||
if (this.animateItem !== null) {
|
||||
this.animateItem.addEventListener('loopComplete', this.callbackItem)
|
||||
promptAction.showToast({
|
||||
message: 'grunt loopComplete'
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -135,6 +135,10 @@
|
||||
{
|
||||
"name": "reverse",
|
||||
"value": "反向"
|
||||
},
|
||||
{
|
||||
"name": "use_ae_fps",
|
||||
"value": "使用AE原始帧率播放动画"
|
||||
}
|
||||
]
|
||||
}
|
@ -5,9 +5,9 @@
|
||||
|
||||
### 效果预览
|
||||
|
||||
| 主键盘 | 菜单 | 编辑 |
|
||||
| :---------------------------------------: | :---------------------------------------: | :--------------------------------------: |
|
||||
| ![main](screenshots/devices/main.jpg) | ![util](screenshots/devices/menu.jpg) | ![convertxml](screenshots/devices/edit.jpg) |
|
||||
| 主键盘 | 菜单 | 编辑 | 预上屏 |
|
||||
| :---------------------------------------: | :---------------------------------------: | :--------------------------------------: |:--------------------------------------: |
|
||||
| ![main](screenshots/devices/main.jpg) | ![util](screenshots/devices/menu.jpg) | ![convertxml](screenshots/devices/edit.jpg) | ![preview](screenshots/devices/preview.jpg)
|
||||
|
||||
使用说明
|
||||
|
||||
@ -23,6 +23,12 @@
|
||||
|
||||
6.编辑状态点击选择按钮,进入选择状态,点击方向键可以选中文本。
|
||||
|
||||
预上屏应用使用说明
|
||||
1.安装应用,首页点击kikainput切换输入法到当前应用;
|
||||
2.文本框中输入预上屏触发字符'hel',触发输入法预上屏;
|
||||
3.点击输入法的回车键,确认预上屏内容'hello world'替换文本框中的'hel';
|
||||
注:上屏的内容后继续输入字符a,hello world被替换,就是没有确认上屏,否则呈现内容是hello worlda的话就是内容已经确认预上屏。
|
||||
|
||||
### 工程目录
|
||||
|
||||
```
|
||||
@ -35,8 +41,11 @@ KikaInput
|
||||
│ │ ├── common
|
||||
│ │ │ ├── StyleConfiguration.ets //适配不同设备下的键盘布局
|
||||
│ │ ├── components //输入法软键盘自定义组件
|
||||
│ │ ├── entryability //应用入口
|
||||
│ │ │ ├── EntryAbility.ets //应用入口Ability
|
||||
│ │ ├── pages
|
||||
│ │ │ ├── Index.ets //输入法主页
|
||||
│ │ │ ├── PrivatePreview.ets //预上屏主页
|
||||
│ │ ├── model
|
||||
│ │ │ ├── HardKeyUtils.ets //外接键盘KeyCode数据
|
||||
│ │ │ ├── KeyboardController.ets //输入法键盘控制
|
||||
@ -72,9 +81,9 @@ KikaInput
|
||||
|
||||
1.本示例仅支持标准系统上运行。
|
||||
|
||||
2.本示例适配API10版本SDK,SDK版本号(API Version 10 Release),镜像版本号(4.0 Release)。
|
||||
2.本示例适配API12版本SDK,SDK版本号(API Version 12 Release),镜像版本号(5.0.0.25及以后版本)。
|
||||
|
||||
3.本示例需要使用DevEco Studio 版本号(4.0 Release)及以上版本才可编译运行。
|
||||
3.本示例需要使用DevEco Studio 版本号(4.1 Release)及以上版本才可编译运行。
|
||||
|
||||
5.本示例需要使用@ohos.application.InputMethodExtensionAbility系统权限的系统接口。使用Full SDK时需要手动从镜像站点获取,并在DevEco Studio中替换,具体操作可参考[替换指南](https://docs.openharmony.cn/pages/v3.2/zh-cn/application-dev/quick-start/full-sdk-switch-guide.md/)。
|
||||
|
||||
|
@ -15,13 +15,14 @@
|
||||
|
||||
{
|
||||
"app": {
|
||||
"signingConfigs": [],
|
||||
"signingConfigs": [
|
||||
],
|
||||
"products": [
|
||||
{
|
||||
"name": "default",
|
||||
"signingConfig": "default",
|
||||
"compileSdkVersion": 10,
|
||||
"compatibleSdkVersion": 10,
|
||||
"compileSdkVersion": 12,
|
||||
"compatibleSdkVersion": 12,
|
||||
"runtimeOS": "OpenHarmony"
|
||||
}
|
||||
]
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Huawei Device Co., Ltd.
|
||||
* Copyright (c) 2023-2024 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
|
||||
@ -62,6 +62,7 @@ export struct ReturnItem {
|
||||
.width(this.returnWidth)
|
||||
.borderRadius(4)
|
||||
.height('100%')
|
||||
.id('returnItem')
|
||||
.shadow({ radius: 1, color: $r('app.color.shadow'), offsetY: 3 })
|
||||
.onClick(() => {
|
||||
InputHandler.getInstance().sendKeyFunction();
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 InputStyle 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';
|
||||
|
||||
export default class EntryAbility extends UIAbility {
|
||||
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
|
||||
}
|
||||
|
||||
onDestroy(): void {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
|
||||
}
|
||||
|
||||
onWindowStageCreate(windowStage: window.WindowStage): void {
|
||||
// Main window is created, set main page for this ability
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
|
||||
|
||||
windowStage.loadContent('pages/PrivatePreview', (err) => {
|
||||
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.');
|
||||
});
|
||||
}
|
||||
|
||||
onWindowStageDestroy(): void {
|
||||
// Main window is destroyed, release UI related resources
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
|
||||
}
|
||||
|
||||
onForeground(): void {
|
||||
// Ability has brought to foreground
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
|
||||
}
|
||||
|
||||
onBackground(): void {
|
||||
// Ability has back to background
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Huawei Device Co., Ltd.
|
||||
* Copyright (c) 2023-2024 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
|
||||
@ -35,7 +35,7 @@ interface CursorInfo {
|
||||
y: number;
|
||||
height: number
|
||||
}
|
||||
|
||||
const previewContent: string = 'hello world';
|
||||
const InputMethodEngine: inputMethodEngine.InputMethodAbility = inputMethodEngine.getInputMethodAbility();
|
||||
const TAG: string = 'KeyboardController->';
|
||||
const isDebug: boolean = false;
|
||||
@ -69,6 +69,7 @@ export class InputHandler {
|
||||
private selectInfo: string = '';
|
||||
private textInfo: string = '';
|
||||
private inputInfo: string = '';
|
||||
private intputText: string = '';
|
||||
|
||||
private constructor() {
|
||||
this.mKbController = undefined;
|
||||
@ -94,8 +95,28 @@ export class InputHandler {
|
||||
this.mEditorAttribute = res;
|
||||
AppStorage.setOrCreate('enterKeyType', res.enterKeyType);
|
||||
AppStorage.setOrCreate('inputPattern', res.inputPattern);
|
||||
this.setInputInfo('EditorInfo:enterKeyType = ' + this.mEditorAttribute.enterKeyType + ';inputPattern = ' + this.mEditorAttribute.inputPattern);
|
||||
|
||||
this.setInputInfo('EditorInfo:enterKeyType = ' + this.mEditorAttribute.enterKeyType + ';inputPattern = ' + this.mEditorAttribute.inputPattern +
|
||||
'; isTextPreviewSupported = ' + this.mEditorAttribute.isTextPreviewSupported);
|
||||
})
|
||||
try {
|
||||
this.addLog(`onInputStart sendPrivateCommand begin`);
|
||||
let record: Record<string, inputMethodEngine.CommandDataType> = {
|
||||
'previewTextStyle': 'underline'
|
||||
};
|
||||
|
||||
this.mTextInputClient.sendPrivateCommand(record).then((err) => {
|
||||
this.addLog(`insertText sendPrivateCommand success`);
|
||||
}).catch((err:BusinessError) => {
|
||||
if (err !== undefined) {
|
||||
let error = err as BusinessError;
|
||||
this.addLog(`insertText sendPrivateCommand catch error: ${error.code} ${error.message}`);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
let error = err as BusinessError;
|
||||
this.addLog(`insertText sendPrivateCommand catch error: ${error.code} ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
public hideKeyboardSelf(): void {
|
||||
@ -111,6 +132,13 @@ export class InputHandler {
|
||||
this.addLog('sendKeyFunction')
|
||||
if (this.mTextInputClient && this.mEditorAttribute) {
|
||||
this.mTextInputClient.sendKeyFunction(this.mEditorAttribute.enterKeyType);
|
||||
|
||||
//调用结束预上屏接口, 预上屏内容将被系统正式上屏
|
||||
this.mTextInputClient.finishTextPreview().then(() => {
|
||||
this.addLog('Succeeded in finishing text preview.');
|
||||
}).catch((err: BusinessError) => {
|
||||
console.error(`Failed to finishTextPreview: ${JSON.stringify(err)}`);
|
||||
});
|
||||
} else {
|
||||
this.addLog('sendKeyFunction this.mTextInputClient is undefined');
|
||||
}
|
||||
@ -144,12 +172,66 @@ export class InputHandler {
|
||||
this.addLog(`insertText = ${text}`);
|
||||
if (this.mTextInputClient !== undefined) {
|
||||
this.mTextInputClient.insertText(text);
|
||||
|
||||
let indexCursor: number = 0;
|
||||
indexCursor = this.mTextInputClient.getTextIndexAtCursorSync();
|
||||
this.intputText = this.mTextInputClient.getForwardSync(indexCursor);
|
||||
this.intputText = this.intputText + text;
|
||||
|
||||
//设置预上屏数据
|
||||
let length = 2;
|
||||
let textPre: string = this.mTextInputClient.getForwardSync(length);
|
||||
this.addLog('insertText textPre:' + textPre);
|
||||
|
||||
// 取出的内容为当前输入之前的字符,不包含当前的字符,需将当前字符加上
|
||||
textPre = textPre + text;
|
||||
if (textPre == 'hel') {
|
||||
this.addLog('insertText hel');
|
||||
try {
|
||||
//把当前字符计算在内
|
||||
let endRange: number = indexCursor + 1;
|
||||
this.addLog('insertText start ' + (indexCursor - length).toString());
|
||||
this.addLog('insertText end ' + (indexCursor).toString());
|
||||
let range: inputMethodEngine.Range = { start: indexCursor - length, end: endRange };
|
||||
this.mTextInputClient.setPreviewText(previewContent, range).then(() => {
|
||||
this.addLog('insertText preViewText Succeeded in setting preview text.');
|
||||
}).catch((err: BusinessError) => {
|
||||
this.addLog(`insertText preViewText Failed to setPreviewText: ${JSON.stringify(err)}`);
|
||||
});
|
||||
} catch (err) {
|
||||
this.addLog(`insertText preViewText Failed to setPreviewText: ${JSON.stringify(err)}`);
|
||||
}
|
||||
} else if (this.intputText.length > length + 1) {
|
||||
this.addLog('insertText this.intputText ' + this.intputText);
|
||||
|
||||
let indexSubStrStart: number = this.intputText.lastIndexOf('hel');
|
||||
this.addLog('insertText indexSubStrStart ' + indexSubStrStart.toString());
|
||||
|
||||
if (indexSubStrStart >= 0) {
|
||||
// 当前文本框存在预上屏的部分内容
|
||||
let subStr: string = this.intputText.substring(indexSubStrStart, indexCursor);
|
||||
this.addLog('insertText indexSubStrStart subStr ' + subStr);
|
||||
|
||||
if((previewContent != subStr) && (previewContent.includes(subStr))) {
|
||||
// 文本框的子串与预上屏内容吻合,则调用预上屏接口,替换文本框的子串
|
||||
this.addLog('insertText previewContent indexSubStrStart ' + indexSubStrStart.toString());
|
||||
this.addLog('insertText previewContent end ' + (indexCursor).toString());
|
||||
let range: inputMethodEngine.Range = { start: indexSubStrStart, end: indexCursor + 1 };
|
||||
this.mTextInputClient.setPreviewText(previewContent, range).then(() => {
|
||||
this.addLog('insertText preViewText Succeeded in setting preview text.');
|
||||
}).catch((err: BusinessError) => {
|
||||
this.addLog(`insertText preViewText Failed to setPreviewText: ${JSON.stringify(err)}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.addLog('insertText this.mTextInputClient is undefined');
|
||||
}
|
||||
if (isDebug) {
|
||||
this.refreshInfo();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public addLog(message: string): void {
|
||||
@ -436,6 +518,19 @@ class KeyboardController {
|
||||
}
|
||||
});
|
||||
|
||||
this.inputHandle.addLog('pre on privateCommand');
|
||||
try {
|
||||
InputMethodEngine.on('privateCommand', (record : Record<string, inputMethodEngine.CommandDataType>) => {
|
||||
this.inputHandle.addLog('keyboard privateCommand' + JSON.stringify(record));
|
||||
Object.keys(record).forEach((key: string) => {
|
||||
this.inputHandle.addLog(`onPageShow private command key: ${key}, value: ${record[key]}`);
|
||||
})
|
||||
});
|
||||
} catch (err) {
|
||||
let error = err as BusinessError;
|
||||
this.inputHandle.addLog(`on privateCommand sendPrivateCommand catch error: ${error.code} ${error.message}`);
|
||||
}
|
||||
|
||||
this.mKeyboardDelegate = inputMethodEngine.getKeyboardDelegate();
|
||||
|
||||
this.mKeyboardDelegate.on('keyDown', (keyEvent: inputMethodEngine.KeyEvent) => {
|
||||
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 InputStyle KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import inputMethod from '@ohos.inputMethod';
|
||||
import Log from '../model/Log';
|
||||
import { BusinessError } from '@ohos.base';
|
||||
|
||||
const TAG: string = 'PrivatePreview->';
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct PrivatePreview {
|
||||
@State message: string = 'Hello World';
|
||||
@State accountText: string = '';
|
||||
@State preViewText: string = '';
|
||||
@StorageLink('isTextPreviewSupported') isTextPreviewSupported: boolean = true;
|
||||
controllerPrivate: TextInputController = new TextInputController();
|
||||
controllerPreview: TextInputController = new TextInputController();
|
||||
|
||||
aboutToAppear() {
|
||||
this.addLog(`private preview aboutToAppear begin!`);
|
||||
inputMethod.getSetting().showOptionalInputMethods().then((data: boolean) => {
|
||||
console.log('onPageShow Succeeded in showing optionalInputMethods.');
|
||||
}).catch((err: BusinessError) => {
|
||||
console.error(`onPageShow Failed to showOptionalInputMethods: ${JSON.stringify(err)}`);
|
||||
})
|
||||
}
|
||||
|
||||
onPageShow() {
|
||||
this.addLog(`private command onPageShow!`);
|
||||
}
|
||||
|
||||
addLog(message: string): void {
|
||||
Log.showInfo(TAG, `kikaInput-new: ${message}`);
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Text($r('app.string.Preview_Text'))
|
||||
.width('100%')
|
||||
.height(56)
|
||||
.fontFamily('HarmonyHeiTi-Medium')
|
||||
.fontSize('16fp')
|
||||
.fontColor('#182431')
|
||||
.lineHeight(22)
|
||||
.fontWeight(500)
|
||||
|
||||
TextInput({
|
||||
text: this.preViewText,
|
||||
placeholder: $r('app.string.Preview_tip'),
|
||||
controller: this.controllerPreview
|
||||
})
|
||||
.placeholderFont({ size: 16, weight: 400 })
|
||||
.width('100%')
|
||||
.height(56)
|
||||
.margin(10)
|
||||
.fontSize(16)
|
||||
.fontColor('#182431')
|
||||
.backgroundColor('#FFFFFF')
|
||||
.border({ width: 1, color: Color.Gray, radius: 18 })
|
||||
.maxLength(50)
|
||||
.onChange((value: string) => {
|
||||
this.addLog('preViewText onChange.');
|
||||
})
|
||||
.onSubmit(() => {
|
||||
this.addLog('preViewText onSubmit.');
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.padding(20)
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Huawei Device Co., Ltd.
|
||||
* Copyright (c) 2023-2024 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
|
||||
@ -19,7 +19,7 @@
|
||||
"type": "entry",
|
||||
"srcEntry": "./ets/Application/AbilityStage.ets",
|
||||
"description": "$string:entry_desc",
|
||||
"mainElement": "MainAbility",
|
||||
"mainElement": "EntryAbility",
|
||||
"deviceTypes": [
|
||||
"default",
|
||||
"tablet"
|
||||
@ -42,6 +42,28 @@
|
||||
"type": "inputMethod",
|
||||
"exported": true
|
||||
}
|
||||
],
|
||||
"abilities": [
|
||||
{
|
||||
"name": "EntryAbility",
|
||||
"srcEntry": "./ets/entryability/EntryAbility.ets",
|
||||
"description": "$string:EntryAbility_desc",
|
||||
"icon": "$media:icon",
|
||||
"label": "$string:EntryAbility_label",
|
||||
"startWindowIcon": "$media:startIcon",
|
||||
"startWindowBackground": "$color:start_window_background",
|
||||
"exported": true,
|
||||
"skills": [
|
||||
{
|
||||
"entities": [
|
||||
"entity.system.home"
|
||||
],
|
||||
"actions": [
|
||||
"action.system.home"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -43,6 +43,10 @@
|
||||
{
|
||||
"name": "key_item_normal_text",
|
||||
"value": "#606060"
|
||||
},
|
||||
{
|
||||
"name": "start_window_background",
|
||||
"value": "#FFFFFF"
|
||||
}
|
||||
]
|
||||
}
|
@ -23,6 +23,30 @@
|
||||
{
|
||||
"name": "edit",
|
||||
"value": "Edit"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_desc",
|
||||
"value": "description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_label",
|
||||
"value": "label"
|
||||
},
|
||||
{
|
||||
"name": "Private_data",
|
||||
"value": "Private data"
|
||||
},
|
||||
{
|
||||
"name": "Preview_Text",
|
||||
"value": "Preview Text"
|
||||
},
|
||||
{
|
||||
"name": "Private_tip",
|
||||
"value": "Please input account number"
|
||||
},
|
||||
{
|
||||
"name": "Preview_tip",
|
||||
"value": "Please input preview content"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@ -1,5 +1,6 @@
|
||||
{
|
||||
"src": [
|
||||
"pages/Index"
|
||||
"pages/Index",
|
||||
"pages/PrivatePreview"
|
||||
]
|
||||
}
|
||||
|
@ -23,6 +23,30 @@
|
||||
{
|
||||
"name": "edit",
|
||||
"value": "Edit"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_desc",
|
||||
"value": "description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_label",
|
||||
"value": "label"
|
||||
},
|
||||
{
|
||||
"name": "Private_data",
|
||||
"value": "Private data"
|
||||
},
|
||||
{
|
||||
"name": "Preview_Text",
|
||||
"value": "Preview Text"
|
||||
},
|
||||
{
|
||||
"name": "Private_tip",
|
||||
"value": "Please input account number"
|
||||
},
|
||||
{
|
||||
"name": "Preview_tip",
|
||||
"value": "Please input preview content"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -23,6 +23,29 @@
|
||||
{
|
||||
"name": "edit",
|
||||
"value": "编辑"
|
||||
},{
|
||||
"name": "EntryAbility_desc",
|
||||
"value": "description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_label",
|
||||
"value": "label"
|
||||
},
|
||||
{
|
||||
"name": "Private_data",
|
||||
"value": "私有数据传递:"
|
||||
},
|
||||
{
|
||||
"name": "Preview_Text",
|
||||
"value": "预上屏:"
|
||||
},
|
||||
{
|
||||
"name": "Private_tip",
|
||||
"value": "请输入账号信息"
|
||||
},
|
||||
{
|
||||
"name": "Preview_tip",
|
||||
"value": "请输入预上屏内容"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Huawei Device Co., Ltd.
|
||||
* Copyright (c) 2023-2024 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
|
||||
@ -15,9 +15,10 @@
|
||||
|
||||
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
|
||||
import UIAbility from '@ohos.app.ability.UIAbility';
|
||||
import { describe, it, expect } from '@ohos/hypium';
|
||||
import { describe, it, expect, TestType } from '@ohos/hypium';
|
||||
import { Driver, ON, MatchPattern } from '@ohos.UiTest';
|
||||
import { logger } from '../util/Logger';
|
||||
import inputMethod from '@ohos.inputMethod';
|
||||
|
||||
const TAG: string = 'Sample_KikaInput_Test';
|
||||
const BUNDLE = 'KikaInput_';
|
||||
@ -25,7 +26,7 @@ const BUNDLE = 'KikaInput_';
|
||||
export default function abilityTest() {
|
||||
describe('ActsAbilityTest', () => {
|
||||
// 打开自绘编辑框应用
|
||||
it(BUNDLE + 'StartAbility_001', 0, async (done: Function) => {
|
||||
it('StartAbility_001',TestType.FUNCTION, async (done: Function) => {
|
||||
logger.info(TAG, `${BUNDLE}StartAbility_001 begin`);
|
||||
let driver: Driver = Driver.create();
|
||||
let delegator = AbilityDelegatorRegistry.getAbilityDelegator();
|
||||
@ -44,31 +45,28 @@ export default function abilityTest() {
|
||||
})
|
||||
|
||||
// 切换输入法应用
|
||||
it(BUNDLE + 'ChangeInputMethod_001', 0, async (done: Function) => {
|
||||
logger.info(TAG, `${BUNDLE}ChangeInputMethod_001 begin`);
|
||||
it('SwitchInputMethod_001',TestType.FUNCTION, async (done: Function) => {
|
||||
logger.info(TAG, `${BUNDLE}SwitchInputMethod_001 begin`);
|
||||
let driver: Driver = Driver.create();
|
||||
let delegator = AbilityDelegatorRegistry.getAbilityDelegator();
|
||||
await delegator.executeShellCommand('aa start ability -a InputMethod -b cn.openharmony.inputmethodchoosedialog')
|
||||
.then(result => {
|
||||
logger.info(TAG, `${BUNDLE}start ability finished, result = ${JSON.stringify(result)}`);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
logger.info(TAG, `${BUNDLE}start ability failed, err = ${JSON.stringify(err)}`);
|
||||
})
|
||||
await inputMethod.getSetting().showOptionalInputMethods();
|
||||
logger.info(TAG, `${BUNDLE}SwitchInputMethod_001 11`);
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.type('List'));
|
||||
logger.info(TAG, `${BUNDLE}SwitchInputMethod_001 22`);
|
||||
|
||||
let list = await driver.findComponent(ON.type('List'));
|
||||
let kikainput = await list.scrollSearch(ON.text('kikainput', MatchPattern.CONTAINS));
|
||||
logger.info(TAG, `${BUNDLE}SwitchInputMethod_001 33 kikainput` + kikainput);
|
||||
await driver.delayMs(1000);
|
||||
let bound = await kikainput.getBounds();
|
||||
await driver.click(bound.right - 10, bound.bottom - 10);
|
||||
await driver.delayMs(1000);
|
||||
logger.info(TAG, `${BUNDLE}ChangeInputMethod_001 end`);
|
||||
logger.info(TAG, `${BUNDLE}SwitchInputMethod_001 end`);
|
||||
done();
|
||||
})
|
||||
|
||||
// 点击自绘编辑框拉起输入法,并输入内容
|
||||
it(BUNDLE + 'ShowKeyboard_001', 0, async (done: Function) => {
|
||||
it('ShowKeyboard_001', TestType.FUNCTION, async (done: Function) => {
|
||||
logger.info(TAG, `${BUNDLE}ShowKeyboard_001 begin`);
|
||||
let driver: Driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
@ -84,6 +82,7 @@ export default function abilityTest() {
|
||||
await keyboardMenu.click();
|
||||
}
|
||||
|
||||
logger.info(TAG, `${BUNDLE}ShowKeyboard_001 11`);
|
||||
// 如果当前是编辑页面,切换到主键盘
|
||||
let btn_selection = await driver.findComponent(ON.id('btn_selection'));
|
||||
if (btn_selection !== undefined && btn_selection !== null) {
|
||||
@ -97,21 +96,36 @@ export default function abilityTest() {
|
||||
if (charKey !== undefined && charKey !== null) {
|
||||
await charKey.click();
|
||||
}
|
||||
logger.info(TAG, `${BUNDLE}ShowKeyboard_001 22`);
|
||||
|
||||
//点击等输入法弹出
|
||||
await customInputText.click();
|
||||
await driver.delayMs(1000);
|
||||
await driver.assertComponentExist(ON.text('a', MatchPattern.CONTAINS));
|
||||
let inputText = await driver.findComponent(ON.text('a', MatchPattern.CONTAINS));
|
||||
await inputText.click();
|
||||
await inputText.click();
|
||||
await inputText.click();
|
||||
let inputTexta = await driver.findComponent(ON.text('a', MatchPattern.CONTAINS));
|
||||
await inputTexta.click();
|
||||
await driver.delayMs(2000);
|
||||
|
||||
await driver.assertComponentExist(ON.text('c', MatchPattern.CONTAINS));
|
||||
let inputTextc = await driver.findComponent(ON.text('c', MatchPattern.CONTAINS));
|
||||
await inputTextc.click();
|
||||
await driver.delayMs(2000);
|
||||
|
||||
await driver.assertComponentExist(ON.text('d', MatchPattern.CONTAINS));
|
||||
let inputTextd = await driver.findComponent(ON.text('d', MatchPattern.CONTAINS));
|
||||
await inputTextd.click();
|
||||
await driver.delayMs(2000);
|
||||
|
||||
let inputContent = await driver.findComponent(ON.id('inputText'));
|
||||
let inputString = await inputContent.getText();
|
||||
expect(inputString === 'aaa').assertTrue();
|
||||
await driver.delayMs(200);
|
||||
expect(inputString === 'acd').assertTrue();
|
||||
logger.info(TAG, `${BUNDLE}ShowKeyboard_001 end`);
|
||||
done();
|
||||
})
|
||||
|
||||
// 进入菜单
|
||||
it(BUNDLE + 'EditModel_001', 0, async (done: Function) => {
|
||||
it('EditModel_001', TestType.FUNCTION, async (done: Function) => {
|
||||
logger.info(TAG, `${BUNDLE}EditModel_001 begin`);
|
||||
let driver: Driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
@ -129,7 +143,7 @@ export default function abilityTest() {
|
||||
})
|
||||
|
||||
// 编辑界面,向左移动光标
|
||||
it(BUNDLE + 'MoveCursor_001', 0, async (done: Function) => {
|
||||
it('MoveCursor_001', TestType.FUNCTION, async (done: Function) => {
|
||||
logger.info(TAG, `${BUNDLE}MoveCursor_001 begin`);
|
||||
let driver: Driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
@ -147,7 +161,7 @@ export default function abilityTest() {
|
||||
})
|
||||
|
||||
// 编辑界面,向右移动光标
|
||||
it(BUNDLE + 'MoveCursor_002', 0, async (done: Function) => {
|
||||
it('MoveCursor_002', TestType.FUNCTION, async (done: Function) => {
|
||||
logger.info(TAG, `${BUNDLE}MoveCursor_002 begin`);
|
||||
let driver: Driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
@ -165,7 +179,7 @@ export default function abilityTest() {
|
||||
})
|
||||
|
||||
// 编辑界面,文本选中
|
||||
it(BUNDLE + 'Selection_001', 0, async (done: Function) => {
|
||||
it('Selection_001', TestType.FUNCTION, async (done: Function) => {
|
||||
logger.info(TAG, `${BUNDLE}Selection_001 begin`);
|
||||
let driver: Driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
@ -190,7 +204,7 @@ export default function abilityTest() {
|
||||
})
|
||||
|
||||
// 隐藏软键盘
|
||||
it(BUNDLE + 'HideKeyboard_001', 0, async (done: Function) => {
|
||||
it('HideKeyboard_001', TestType.FUNCTION, async (done: Function) => {
|
||||
logger.info(TAG, `${BUNDLE}HideKeyboard_001 begin`);
|
||||
let driver: Driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
@ -203,5 +217,117 @@ export default function abilityTest() {
|
||||
logger.info(TAG, `${BUNDLE}HideKeyboard_001 end`);
|
||||
done();
|
||||
})
|
||||
|
||||
// 预上屏
|
||||
it('PreviewText_001', TestType.FUNCTION, async (done: Function) => {
|
||||
logger.info(TAG, `${BUNDLE}PreviewText_001 begin`);
|
||||
let driver: Driver = Driver.create();
|
||||
let delegator = AbilityDelegatorRegistry.getAbilityDelegator();
|
||||
|
||||
await delegator.executeShellCommand('aa start ability -a EntryAbility -b com.samples.kikainput')
|
||||
.then(result => {
|
||||
logger.info(TAG, `${BUNDLE}start ability finished, result = ${JSON.stringify(result)}`);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
logger.info(TAG, `${BUNDLE}start ability failed, err = ${JSON.stringify(err)}`);
|
||||
})
|
||||
logger.info(TAG, `${BUNDLE}PreviewText_001 11`);
|
||||
await driver.delayMs(1000);
|
||||
|
||||
let kikaInputDown = await driver.findComponent(ON.text('kikainput', MatchPattern.CONTAINS));
|
||||
await kikaInputDown.click();
|
||||
await driver.delayMs(1000);
|
||||
logger.info(TAG, `${BUNDLE}PreviewText_001 end`);
|
||||
done();
|
||||
})
|
||||
|
||||
it('PreviewText_002', TestType.FUNCTION, async (done: Function) => {
|
||||
logger.info(TAG, `${BUNDLE}PreviewText_002 begin`);
|
||||
let driver: Driver = Driver.create();
|
||||
await driver.delayMs(1000);
|
||||
|
||||
// 输入‘hel'触发预上屏'hello world'
|
||||
await driver.assertComponentExist(ON.type('TextInput'));
|
||||
let textInput = await driver.findComponent(ON.type('TextInput'));
|
||||
await textInput.click();
|
||||
await driver.delayMs(1000); // 等待输入法应用弹出
|
||||
|
||||
await driver.assertComponentExist(ON.text('h', MatchPattern.CONTAINS));
|
||||
let inputTextH = await driver.findComponent(ON.text('h', MatchPattern.CONTAINS));
|
||||
await inputTextH.click();
|
||||
|
||||
await driver.assertComponentExist(ON.text('e', MatchPattern.CONTAINS));
|
||||
let inputTextE = await driver.findComponent(ON.text('e', MatchPattern.CONTAINS));
|
||||
await inputTextE.click();
|
||||
|
||||
await driver.assertComponentExist(ON.text('l', MatchPattern.CONTAINS));
|
||||
let inputTextL = await driver.findComponent(ON.text('l', MatchPattern.CONTAINS));
|
||||
await inputTextL.click();
|
||||
await driver.delayMs(2000);
|
||||
|
||||
// 点击回车键触发预上屏内容到编辑框
|
||||
await driver.assertComponentExist(ON.id('returnItem')); // ON.type('ReturnItem')
|
||||
let enterKey = await driver.findComponent(ON.id('returnItem')); // ON.type('ReturnItem')
|
||||
|
||||
await enterKey.click();
|
||||
await driver.delayMs(1000);
|
||||
|
||||
// 校验文本框的内容为预上屏内容
|
||||
let inputString = await textInput.getText();
|
||||
expect(inputString === 'hello world').assertTrue();
|
||||
|
||||
logger.info(TAG, `${BUNDLE}PreviewText_002 end`);
|
||||
done();
|
||||
})
|
||||
|
||||
it('PreviewText_003', TestType.FUNCTION, async (done: Function) => {
|
||||
logger.info(TAG, `${BUNDLE}PreviewText_003 begin`);
|
||||
let driver: Driver = Driver.create();
|
||||
|
||||
// 输入‘abhel'触发预上屏'hello world'
|
||||
await driver.assertComponentExist(ON.type('TextInput'));
|
||||
let textInput = await driver.findComponent(ON.type('TextInput'));
|
||||
await textInput.click();
|
||||
await textInput.clearText();
|
||||
await driver.delayMs(1000); // 等待输入法应用弹出
|
||||
|
||||
await driver.assertComponentExist(ON.text('a', MatchPattern.CONTAINS));
|
||||
let inputText = await driver.findComponent(ON.text('a', MatchPattern.CONTAINS));
|
||||
await inputText.click();
|
||||
|
||||
await driver.assertComponentExist(ON.text('b', MatchPattern.CONTAINS));
|
||||
inputText = await driver.findComponent(ON.text('b', MatchPattern.CONTAINS));
|
||||
await inputText.click();
|
||||
|
||||
await driver.assertComponentExist(ON.text('h', MatchPattern.CONTAINS));
|
||||
let inputTextH = await driver.findComponent(ON.text('h', MatchPattern.CONTAINS));
|
||||
await inputTextH.click();
|
||||
|
||||
await driver.assertComponentExist(ON.text('e', MatchPattern.CONTAINS));
|
||||
let inputTextE = await driver.findComponent(ON.text('e', MatchPattern.CONTAINS));
|
||||
await inputTextE.click();
|
||||
|
||||
await driver.assertComponentExist(ON.text('l', MatchPattern.CONTAINS));
|
||||
let inputTextL = await driver.findComponent(ON.text('l', MatchPattern.CONTAINS));
|
||||
await inputTextL.click();
|
||||
await driver.delayMs(2000);
|
||||
|
||||
logger.info(TAG, `${BUNDLE}PreviewText_003 11`);
|
||||
|
||||
// 点击回车键触发预上屏内容到编辑框
|
||||
await driver.assertComponentExist(ON.id('returnItem')); // ON.type('ReturnItem')
|
||||
let enterKey = await driver.findComponent(ON.id('returnItem')); // ON.type('ReturnItem')
|
||||
|
||||
await enterKey.click();
|
||||
await driver.delayMs(1000);
|
||||
|
||||
// 校验文本框的内容为预上屏内容
|
||||
let inputString = await textInput.getText();
|
||||
expect(inputString === 'abhello world').assertTrue();
|
||||
|
||||
logger.info(TAG, `${BUNDLE}PreviewText_003 end`);
|
||||
done();
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|