diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer \ No newline at end of file diff --git a/AppScope/app.json5 b/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..6ebb4fe2d6e6f0813ec76bea88dc7fd0af5f4afc --- /dev/null +++ b/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.adaptive.video.sample", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:layered_image", + "label": "$string:app_name" + } +} diff --git a/AppScope/resources/base/element/string.json b/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..20ca46b653af201727a2a1d21f219b8b7dcb817f --- /dev/null +++ b/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "adaptive_video_sample" + } + ] +} diff --git a/AppScope/resources/base/media/background.png b/AppScope/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/AppScope/resources/base/media/background.png differ diff --git a/AppScope/resources/base/media/foreground.png b/AppScope/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97014d3e10e5ff511409c378cd4255713aecd85f Binary files /dev/null and b/AppScope/resources/base/media/foreground.png differ diff --git a/AppScope/resources/base/media/layered_image.json b/AppScope/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/AppScope/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.en.md b/README.en.md deleted file mode 100644 index 9b651610e771782b96bd5afe694f664d098a2028..0000000000000000000000000000000000000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# AdaptiveVideo - -#### Description -{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index 3f9bebfec81fe7c72f2be3043ce38cb3af274db7..37cb8210baf6b22f01709f63ae868940d09de9b5 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,54 @@ -# AdaptiveVideo +# 短视频高阶媒体组件 #### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} -#### 软件架构 -软件架构说明 +开发者使用ArkUI短视频高阶组件、自适应沉浸和旋转规则库提供指导,降低原生视频组件适配沉浸式开发成本,提高开发效率。 +#### 效果预览 -#### 安装教程 +![](./screenshots/screenshots.gif) -1. xxxx -2. xxxx -3. xxxx +#### 工程目录 -#### 使用说明 +``` +├──commons +│ └──base/src/main/ets +│ ├──constants +│ │ └──CommonConstants.ets // 常量类 +│ └──model +│ └──VideoDataSource.ets // 懒加载数据源 +├──features +│ ├──adaptive_video_component/src/main/ets +│ │ ├──components +│ │ │ ├──AdaptiveAVPlayer.ets // 沉浸和旋转库实现类 +│ │ │ ├──CustomAdaptiveVideoComponent.ets // 高阶组件实现类 +│ │ │ ├──Home.ets // 底部TAB所在类 +│ │ │ ├──Recommend.ets // 顶部TAB所在类 +│ │ │ ├──VideoControl.ets // 全屏播放播放控制栏 +│ │ └──view +│ │ ├──AVPlayerUtil.ets // 播放工具类 +├──products +│ ├──entry/src/main/ets +│ │ ├──entryability +│ │ │ └──EntryAbility.ets // 程序入口类 +│ │ │ ├──entryability +│ │ │ └──EntryAbility.ets // 程序入口类 +│ │ └──pages + └──Index.ets // 主页面 +``` -1. xxxx -2. xxxx -3. xxxx +#### 依赖 -#### 参与贡献 +依赖自适应视频沉浸式、自适应视频旋转、视频自适应沉浸高阶组件库 -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +库源码地址:https://gitcode.com/openharmony-sig/hadss_adaptive/tree/master/adaptive_video +#### 注意事项 -#### 特技 +1. 本示例运行需要配置签名才能运行到真机 +2. 如果需要修改本地视频源,注意视频源放到features\adaptive_video_component\src\main\resources\rawfile目录下;同时修改E:\project\AdaptiveVideo_0521\commons\base\src\main\ets\constants\CommonConstants中的VIDEO_SOURCE -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +#### 约束与限制 + +1. 本示例仅支持标准系统上运行,支持设备:华为手机、折叠屏、平板、2in1设备。 +2. SDK版本:API14(5.0.2)及以上 \ No newline at end of file diff --git a/build-profile.json5 b/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..ffa0a967fb44736eaf38b705ce5524f6da6c3096 --- /dev/null +++ b/build-profile.json5 @@ -0,0 +1,48 @@ +{ + "app": { + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.0.2(14)", + "runtimeOS": "HarmonyOS", + "buildOption": { + "strictMode": { + "caseSensitiveCheck": true, + "useNormalizedOHMUrl": true + } + } + } + ], + "buildModeSet": [ + { + "name": "debug", + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./products/entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + }, + { + "name": "adaptive_video_component", + "srcPath": "./features/adaptive_video_component" + }, + { + "name": "base", + "srcPath": "./commons/base", + } + ] +} \ No newline at end of file diff --git a/code-linter.json5 b/code-linter.json5 new file mode 100644 index 0000000000000000000000000000000000000000..073990fa45394e1f8e85d85418ee60a8953f9b99 --- /dev/null +++ b/code-linter.json5 @@ -0,0 +1,32 @@ +{ + "files": [ + "**/*.ets" + ], + "ignore": [ + "**/src/ohosTest/**/*", + "**/src/test/**/*", + "**/src/mock/**/*", + "**/node_modules/**/*", + "**/oh_modules/**/*", + "**/build/**/*", + "**/.preview/**/*" + ], + "ruleSet": [ + "plugin:@performance/recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "@security/no-unsafe-aes": "error", + "@security/no-unsafe-hash": "error", + "@security/no-unsafe-mac": "warn", + "@security/no-unsafe-dh": "error", + "@security/no-unsafe-dsa": "error", + "@security/no-unsafe-ecdsa": "error", + "@security/no-unsafe-rsa-encrypt": "error", + "@security/no-unsafe-rsa-sign": "error", + "@security/no-unsafe-rsa-key": "error", + "@security/no-unsafe-dsa-key": "error", + "@security/no-unsafe-dh-key": "error", + "@security/no-unsafe-3des": "error" + } +} \ No newline at end of file diff --git a/commons/base/.gitignore b/commons/base/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/commons/base/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/commons/base/BuildProfile.ets b/commons/base/BuildProfile.ets new file mode 100644 index 0000000000000000000000000000000000000000..3a501e5ddee8ea6d28961648fc7dd314a5304bd4 --- /dev/null +++ b/commons/base/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/commons/base/Index.ets b/commons/base/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..36fc72802db01644db58f7e2dc29683a5a774d43 --- /dev/null +++ b/commons/base/Index.ets @@ -0,0 +1,3 @@ +export { CommonConstants } from './src/main/ets/constants/CommonConstants' + +export { VideoDataSource } from './src/main/ets/model/VideoDataSource' \ No newline at end of file diff --git a/commons/base/build-profile.json5 b/commons/base/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..cda3307123ec7c43181580a86ef7e82a18319a34 --- /dev/null +++ b/commons/base/build-profile.json5 @@ -0,0 +1,28 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + } + ] +} diff --git a/commons/base/consumer-rules.txt b/commons/base/consumer-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/commons/base/hvigorfile.ts b/commons/base/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..42187071482d292588ad40babeda74f7b8d97a23 --- /dev/null +++ b/commons/base/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/commons/base/obfuscation-rules.txt b/commons/base/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/commons/base/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/commons/base/oh-package.json5 b/commons/base/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..ef4cfa6749900e921b0c18f7fb33a61bd9f2be5f --- /dev/null +++ b/commons/base/oh-package.json5 @@ -0,0 +1,9 @@ +{ + "name": "base", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": {} +} diff --git a/commons/base/src/main/ets/constants/CommonConstants.ets b/commons/base/src/main/ets/constants/CommonConstants.ets new file mode 100644 index 0000000000000000000000000000000000000000..8a2e29d7bffafafb78d77089a5948089bc6bf6ce --- /dev/null +++ b/commons/base/src/main/ets/constants/CommonConstants.ets @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2025 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. + * + */ + +/** + * Common constants for all features. + */ +export class CommonConstants { + /** + * AVPlayer state. + */ + public static readonly AV_PLAYER_STATE: string = 'avplayerState'; + /** + * Idle state of avPlayer. + */ + public static readonly AV_PLAYER_IDLE_STATE: string = 'idle'; + /** + * Initialized state of avPlayer. + */ + public static readonly AV_PLAYER_INITIALIZED_STATE: string = 'initialized'; + /** + * Prepared state of avPlayer. + */ + public static readonly AV_PLAYER_PREPARED_STATE: string = 'prepared'; + /** + * Playing state of avPlayer. + */ + public static readonly AV_PLAYER_PLAYING_STATE: string = 'playing'; + /** + * Pause state of avPlayer. + */ + public static readonly AV_PLAYER_PAUSED_STATE: string = 'paused'; + /** + * Completed state of avPlayer. + */ + public static readonly AV_PLAYER_COMPLETED_STATE: string = 'completed'; + /** + * Stopped state of avPlayer. + */ + public static readonly AV_PLAYER_STOPPED_STATE: string = 'stopped'; + /** + * Release state of avPlayer. + */ + public static readonly AV_PLAYER_RELEASE_STATE: string = 'released'; + /** + * Error state of avPlayer. + */ + public static readonly AV_PLAYER_ERROR_STATE: string = 'error'; + /** + * Update time of av player. + */ + public static readonly AV_PLAYER_UPDATE_TIME: string = 'updateTime'; + /** + * Current time of av player. + */ + public static readonly AV_PLAYER_CURRENT_TIME: string = 'currentTime'; + /** + * Total time of av player. + */ + public static readonly AV_PLAYER_TOTAL_TIME: string = 'totalTime'; + /** + * Progress of av player. + */ + public static readonly AV_PLAYER_PROGRESS: string = 'progress'; + /** + * One hundred for progress. + */ + public static readonly PROGRESS_HUNDRED: number = 100; + /** + * One thousand for progress. + */ + public static readonly PROGRESS_THOUSAND: number = 1000; + /** + * Second in hour. + */ + public static readonly SECOND_IN_HOUR: number = 3600; + /** + * Second in minute. + */ + public static readonly SECOND_IN_MINUTE: number = 60; + /** + * colon. + */ + public static readonly COLON: string = ':'; + /** + * Zero. + */ + public static readonly ZERO: number = 0; + /** + * One. + */ + public static readonly ONE: number = 1; + /** + * Ten. + */ + public static readonly TEN: number = 10; + /** + * Time prefix. + */ + public static readonly TIME_PREFIX: string = '0'; + /** + * Empty time. + */ + public static readonly EMPTY_TIME: string = '00' + /** + * 底部导航栏高度 + */ + public static readonly BOTTOM_TAB_HEIGHT: string = 'bottom_tab_height'; + /** + * 系统底部导航条高度 + */ + public static readonly NAVIGATION_INDICATOR_HEIGHT: string = 'bottomRectHeight'; + /** + * 系统顶部状态栏高度 + */ + public static readonly SYSTEM_HEIGHT: string = 'topRectHeight'; + /** + * 窗口大小 + */ + public static readonly WINDOW_SIZE: string = 'windowSize'; + /** + * 本地资源列表 + */ + public static readonly VIDEO_SOURCE: string[] = [ + '1_1.mp4', + '3_4.mp4', + '4_3.mp4', + '9_16_1.mp4', + '9_16_2.mp4', + '16_9.mp4' + ]; +} + + diff --git a/commons/base/src/main/ets/model/VideoDataSource.ets b/commons/base/src/main/ets/model/VideoDataSource.ets new file mode 100644 index 0000000000000000000000000000000000000000..45786f0b46ec1cfb5c8c5196235de0df3380d558 --- /dev/null +++ b/commons/base/src/main/ets/model/VideoDataSource.ets @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import { media } from '@kit.MediaKit'; + +class BasicDataSource implements IDataSource { + private listeners: DataChangeListener[] = []; + + public totalCount(): number { + return 0; + } + + public getData(_index: number): media.AVFileDescriptor | string | undefined { + return undefined; + } + + registerDataChangeListener(listener: DataChangeListener): void { + if (this.listeners.indexOf(listener) < 0) { + this.listeners.push(listener); + } + } + + unregisterDataChangeListener(listener: DataChangeListener): void { + const pos = this.listeners.indexOf(listener); + if (pos >= 0) { + this.listeners.splice(pos, 1); + } + } + + notifyDataReload(): void { + this.listeners.forEach(listener => { + listener.onDataReloaded(); + }) + } + + notifyDataAdd(index: number): void { + this.listeners.forEach(listener => { + listener.onDataAdd(index); + }) + } + + notifyDataChange(index: number): void { + this.listeners.forEach(listener => { + listener.onDataChange(index); + }) + } +} + +/** + * 懒加载数据集 + */ +export class VideoDataSource extends BasicDataSource { + public pathArray: string[] = []; + + constructor(element: Object[]) { + super(); + for (let index = 0; index < element.length; index++) { + this.pathArray.push(element[index] as string); + } + } + + public totalCount(): number { + return this.pathArray.length; + } + + public getData(index: number): string { + return this.pathArray[index]; + } + + public addData(index: number): void { + this.pathArray.splice(index, 0); + this.notifyDataAdd(index); + } +} \ No newline at end of file diff --git a/commons/base/src/main/module.json5 b/commons/base/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7bfd31f43bf6298c83306efcea609eeb1bb3c4a8 --- /dev/null +++ b/commons/base/src/main/module.json5 @@ -0,0 +1,12 @@ +{ + "module": { + "name": "base", + "type": "har", + "deviceTypes": [ + "default", + "tablet", + "2in1", + "car" + ] + } +} diff --git a/commons/base/src/main/resources/base/element/float.json b/commons/base/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..33ea22304f9b1485b5f22d811023701b5d4e35b6 --- /dev/null +++ b/commons/base/src/main/resources/base/element/float.json @@ -0,0 +1,8 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + } + ] +} diff --git a/commons/base/src/main/resources/base/element/string.json b/commons/base/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f51a9c8461a55f6312ef950344e3145b7f82d607 --- /dev/null +++ b/commons/base/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} diff --git a/features/adaptive_video_component/.gitignore b/features/adaptive_video_component/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/features/adaptive_video_component/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/features/adaptive_video_component/BuildProfile.ets b/features/adaptive_video_component/BuildProfile.ets new file mode 100644 index 0000000000000000000000000000000000000000..3a501e5ddee8ea6d28961648fc7dd314a5304bd4 --- /dev/null +++ b/features/adaptive_video_component/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/features/adaptive_video_component/Index.ets b/features/adaptive_video_component/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..2a8a81967c18bcb86c230a9cd4398b6bf06a3066 --- /dev/null +++ b/features/adaptive_video_component/Index.ets @@ -0,0 +1 @@ +export { Home } from './src/main/ets/components/Home'; \ No newline at end of file diff --git a/features/adaptive_video_component/build-profile.json5 b/features/adaptive_video_component/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..cda3307123ec7c43181580a86ef7e82a18319a34 --- /dev/null +++ b/features/adaptive_video_component/build-profile.json5 @@ -0,0 +1,28 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + } + ] +} diff --git a/features/adaptive_video_component/consumer-rules.txt b/features/adaptive_video_component/consumer-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/features/adaptive_video_component/hvigorfile.ts b/features/adaptive_video_component/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..42187071482d292588ad40babeda74f7b8d97a23 --- /dev/null +++ b/features/adaptive_video_component/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/features/adaptive_video_component/libs/adaptive_video.har b/features/adaptive_video_component/libs/adaptive_video.har new file mode 100644 index 0000000000000000000000000000000000000000..0e7f36ee20eed71efb124d043d1b3a7f0bea6741 Binary files /dev/null and b/features/adaptive_video_component/libs/adaptive_video.har differ diff --git a/features/adaptive_video_component/obfuscation-rules.txt b/features/adaptive_video_component/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/features/adaptive_video_component/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/features/adaptive_video_component/oh-package.json5 b/features/adaptive_video_component/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..ddb7dd53b6978f65fd2c9a7d8e980155b042dbbd --- /dev/null +++ b/features/adaptive_video_component/oh-package.json5 @@ -0,0 +1,12 @@ +{ + "name": "adaptive_video_component", + "version": "1.0.0", + "description": "基于adaptive_video的使用", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "base": "file:../../commons/base", + "@hadss/adaptive_video": "file:./libs/adaptive_video.har" + } +} \ No newline at end of file diff --git a/features/adaptive_video_component/src/main/ets/components/AdaptiveAVPlayer.ets b/features/adaptive_video_component/src/main/ets/components/AdaptiveAVPlayer.ets new file mode 100644 index 0000000000000000000000000000000000000000..8123dbf8965920fd8aae853d1536c30f3188cab6 --- /dev/null +++ b/features/adaptive_video_component/src/main/ets/components/AdaptiveAVPlayer.ets @@ -0,0 +1,619 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import { media } from '@kit.MediaKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { CommonConstants } from 'base'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { AVPlayerUtil } from '../utils/AVPlayerUtil'; +import { AdaptiveImmersion, AdaptiveRotation, ScreenMode, ScreenModeNotifier } from '@hadss/adaptive_video'; +import { window } from '@kit.ArkUI'; +import { VideoControl } from './VideoControl'; +import { common } from '@kit.AbilityKit'; +import { BreakpointRange, BreakpointRangeDatabase } from '@hadss/adaptive_video/src/main/ets/rotation/rule/BreakPoint'; +import { PCVideoControl } from './PCVideoControl'; + +const LOG_TAG = 'CustomAVPlayerComponent' +const DOMAIN = 0x0003; +const adaptiveImmersion = AdaptiveImmersion.getInstance(); +const adaptiveRotation = AdaptiveRotation.getInstance(); +const screenModeNotifier: ScreenModeNotifier = ScreenModeNotifier.getInstance(); + +/** + * 自适应沉浸和选择规则的短视频组件 + */ +@Component +export struct AdaptiveAVPlayer { + /** + * 监听当前播放视频索引,提前进行初始化相关动作 + */ + @Prop @Watch('onIndexChange') currentIndex: number = -1; + /** + * 对应视频索引 + */ + index: number = 0; + /** + * 视频播放源 + */ + public currentSource?: media.AVFileDescriptor; + /** + * 播放组件宽度 + */ + @State surfaceWidth: Length = '100%' + /** + * 播放组件高度 + */ + @State surfaceHeight: Length = '100%' + /** + * 控制是否展示全屏播放按钮 + */ + @State showFullScreenBtn: boolean = false; + /** + * 是否隐藏控制栏 + */ + @State hideControl: boolean = true; + /** + * 播放器位置 + */ + @State videoPosition: Position = {} + /** + * 是否隐藏TAB栏,主要控制进入全屏状态后竖屏部分UI信息不展示 + */ + @Consume hideTab: boolean + /** + * 当前播放状态 + */ + @State currentState: media.AVPlayerState = 'idle' + /** + * 当前播放时间 + */ + @State currentTime: string = '' + /** + * 当前播放进度 + */ + @State currentProgress: number = 0 + /** + * 当前窗口大小 + */ + @StorageProp(CommonConstants.WINDOW_SIZE) @Watch('windowChange') windowSize: window.Size = { width: 0, height: 0 } + /** + * 底部TAB栏高度 + */ + @StorageProp(CommonConstants.BOTTOM_TAB_HEIGHT) bottomTabHeight: number = 0 + /** + * 底部避让区域高度 + */ + @StorageProp(CommonConstants.NAVIGATION_INDICATOR_HEIGHT) bottomRectHeight: number = 0 + /** + * 是否是首次播放 + */ + @Link firstInit: boolean + /** + * 视频长度 + */ + @State duration: string = '' + @Link disableSwipe: boolean + /** + * 是否是2in1设备 + */ + @Consume is2in1Device: boolean + /** + * 每个播放组件单独一个avplayer,互不影响 + */ + private avPlayer: media.AVPlayer | undefined = undefined; + private xComponentController: XComponentController = new XComponentController(); + private surfaceId: string = ''; + /** + * 视频播放是否已经准备完成 + */ + private hasPrepared: boolean = false; + /** + * 播放组件原始宽度 + */ + private oriSurfaceWidth: Length = '100%' + /** + * 播放组件原始高度 + */ + private oriSurfaceHeight: Length = '100%' + /** + * 当前播放器是全屏还是非全屏 + */ + private currentScreen: string = ScreenMode.NOT_FULL_SCREEN; + + build() { + RelativeContainer() { + Stack() { + // 额外套一层确保视频是居中的 + Column() { + XComponent({ + id: 'AdaptiveAVPlayer', + type: XComponentType.SURFACE, + controller: this.xComponentController + }) + .width(this.surfaceWidth) + .height(this.surfaceHeight) + .position(this.videoPosition) + .onLoad(() => { + // surfaceID用于播放画面显示 + this.surfaceId = this.xComponentController.getXComponentSurfaceId() + hilog.debug(DOMAIN, LOG_TAG, `AdaptiveAVPlayer load succ, surfaceId = ${this.surfaceId}`) + this.initAVPlayer() + }) + .gesture(TapGesture({ count: 1 }) + .onAction((_event: GestureEvent) => { + hilog.debug(DOMAIN, LOG_TAG, `gesture singleClick`) + // 单击事件处理 + if (this.hideTab && this.hideControl) { + // 隐藏TAB栏并且隐藏控制栏的场景,表示横屏未展示控制栏的状态 + this.hideControl = false + } else { + if (this.currentState === CommonConstants.AV_PLAYER_PLAYING_STATE) { + this.avPlayer?.pause() + } else if (this.currentState === CommonConstants.AV_PLAYER_PAUSED_STATE) { + if (this.canPlay()) { + this.avPlayer?.play() + } + } + } + })) + } + .width('100%') + .height('100%') + + if (this.currentState === CommonConstants.AV_PLAYER_PAUSED_STATE) { + Image($r('app.media.icon_pause')) + .height(56) + .onClick(() => { + if (this.canPlay()) { + this.avPlayer?.play() + } + }) + } + } + .width('100%') + .height('100%') + .alignSelf(ItemAlign.Center) + .align(Alignment.Center) + .alignContent(Alignment.Center) + + Column() { + if (this.showFullScreenBtn) { + Row() { + Image($r('app.media.icon_fullscreen')) + .width(15) + Text($r('app.string.btn_fullscreen')) + .fontSize(14) + .lineHeight(20) + .fontColor(Color.White) + .fontWeight(500) + .margin({ left: 4 }) + } + .id('btn_fullscreen') + .height(28) + .padding({ + left: 8, + right: 8, + top: 4, + bottom: 4 + }) + .borderRadius(14) + .backgroundColor($r("app.color.color_white_ninety_percent")) + .margin({ top: this.surfaceHeight }) + .focusable(false) + .onClick(() => { + hilog.debug(DOMAIN, LOG_TAG, `change fullscreen`) + this.setOrientationFullScreen(); + }) + } + } + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.End) + .width(this.surfaceWidth) + .position(this.videoPosition) + .padding({ top: 15 }) + .hitTestBehavior(HitTestMode.None) + + Column({ space: 20 }) { + Image($r('app.media.menu_icon_user_avatar')).width(44) + this.showFeatureIcon($r('app.media.menu_icon_like'), '1557') + this.showFeatureIcon($r('app.media.menu_icon_comment'), '566') + this.showFeatureIcon($r('app.media.menu_icon_share'), '147') + Image($r('app.media.menu_icon_music_cover')).width(32) + } + .id('featureIcons') + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .margin({ bottom: this.is2in1Device ? 0 : this.bottomTabHeight + this.bottomRectHeight }) + .padding({ bottom: this.is2in1Device ? 84 : 40, right: this.is2in1Device ? 32 : 16 }) + .visibility(this.hideTab ? Visibility.None : Visibility.Visible) + + if (this.is2in1Device) { + Column() { + Row({ space: 4 }) { + Text() + .width(82) + .height(16) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .borderRadius(16) + Text() + .width(55) + .height(14) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .borderRadius(14) + } + + Text() + .width(669) + .height(14) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .margin({ top: 16 }) + .borderRadius(14) + } + .id('colorBlocks') + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + left: { anchor: '__container__', align: HorizontalAlign.Start } + }) + .padding({ bottom: 84, left: 32 }) + .visibility(this.hideTab ? Visibility.None : Visibility.Visible) + .alignItems(HorizontalAlign.Start) + + PCVideoControl() + .id('pc_control') + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + left: { anchor: '__container__', align: HorizontalAlign.Start } + }) + } else { + Column() { + Row({ space: 4 }) { + Text() + .width(82) + .height(16) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .borderRadius(16) + Text() + .width(55) + .height(14) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .borderRadius(14) + } + + Text() + .width(240) + .height(14) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .margin({ top: 11 }) + .borderRadius(14) + + Text() + .width(240) + .height(14) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .margin({ top: 13 }) + .borderRadius(14) + + Text() + .width(93) + .height(14) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .margin({ top: 16 }) + .borderRadius(14) + } + .id('colorBlocks') + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + left: { anchor: '__container__', align: HorizontalAlign.Start } + }) + .margin({ bottom: this.bottomTabHeight + this.bottomRectHeight }) + .padding({ bottom: 40, left: 16 }) + .visibility(this.hideTab ? Visibility.None : Visibility.Visible) + .alignItems(HorizontalAlign.Start) + + if (this.hideTab) { + if (!this.hideControl) { + Row({ space: 8 }) { + Image($r('app.media.fullscreen_back')) + .width(40) + .onClick(() => { + this.changePortraitVideo() + }) + Text($r('app.string.title')) + .fontSize(16) + .lineHeight(21) + .fontColor(Color.White) + } + .margin({ + left: 16, + right: 16, + top: 8, + bottom: 8 + }) + + VideoControl() + .id('video_control') + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + left: { anchor: '__container__', align: HorizontalAlign.Start }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .margin({ bottom: this.bottomRectHeight, left: 16, right: 16 }) + } + } else { + Column() { + Progress({ value: this.currentProgress, type: ProgressType.Linear }) + .id('video_progress') + .color(Color.White) + .height(4) + } + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + left: { anchor: '__container__', align: HorizontalAlign.Start }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .margin({ bottom: this.bottomTabHeight + this.bottomRectHeight, left: 16, right: 16 }) + .height(24) + .justifyContent(FlexAlign.Center) + } + } + } + } + + @Builder + showFeatureIcon(src: ResourceStr, content?: ResourceStr) { + Column() { + Image(src) + .width(30) + if (content) { + Text(content) + .fontSize(14) + .lineHeight(19) + .fontColor(Color.White) + .margin({ top: 5 }) + } + } + } + + onIndexChange() { + if (this.currentIndex !== this.index) { + // 因为会出现多个实例,所以此处暂停掉其他播放的视频 + this.avPlayer?.pause() + } else { + this.changePortraitVideo(); + if (this.hasPrepared) { + if (this.canPlay()) { + this.avPlayer?.play() + } + } else { + this.initAVPlayer() + } + } + } + + initAVPlayer() { + media.createAVPlayer().then((avplayer: media.AVPlayer) => { + if (avplayer === undefined) { + hilog.error(DOMAIN, LOG_TAG, `createAVPlayer fail.`); + return; + } + this.avPlayer = avplayer; + this.addAVPlayerCallback() + if (this.currentSource) { + this.avPlayer.fdSrc = this.currentSource; + } + }) + } + + windowChange() { + if (this.currentIndex === this.index) { + if (this.currentScreen === ScreenMode.FULL_SCREEN) { + // 当需要进入横向布局时的ui布局切换逻辑 (如横向视频进入全屏播放时) + this.setOrientationFullScreen(); + } else { + // 当需要进入竖向布局时的ui布局切换逻辑 (如横向视频退出全屏时) + this.changePortraitVideo(); + } + } + } + + aboutToAppear(): void { + // 获取UIAbilityContext + const context = getContext() as common.UIAbilityContext; + adaptiveImmersion.init(context) + adaptiveRotation.init(context) + screenModeNotifier.init(context) + // 注册屏幕模式监听器 + screenModeNotifier.onScreenModeChange(this.callback); + } + + aboutToDisappear(): void { + this.removeAVPlayerCallback() + this.avPlayer?.release() + // 注销屏幕模式监听器 + screenModeNotifier.offScreenModeChange(this.callback); + } + + private canPlay() { + return (this.avPlayer?.state === CommonConstants.AV_PLAYER_PREPARED_STATE || + this.avPlayer?.state === CommonConstants.AV_PLAYER_PAUSED_STATE || + this.avPlayer?.state === CommonConstants.AV_PLAYER_COMPLETED_STATE) + } + + private addAVPlayerCallback() { + this.avPlayer?.on('stateChange', this.onStateChange); + this.avPlayer?.on('videoSizeChange', this.videoSizeChange); + this.avPlayer?.on('timeUpdate', this.onTimeUpdateFunction); + } + + private removeAVPlayerCallback() { + this.avPlayer?.off('stateChange', this.onStateChange); + this.avPlayer?.off('videoSizeChange', this.videoSizeChange); + this.avPlayer?.off('timeUpdate', this.onTimeUpdateFunction); + } + + /** + * 视频状态监听 + */ + private onStateChange: (state: media.AVPlayerState) => void = async (state: media.AVPlayerState) => { + if (this.avPlayer === undefined) { + console.error(`AvPlayer is undefined`); + return; + } + this.currentState = state + hilog.info(DOMAIN, LOG_TAG, `AVPlayer state is ${state}, index is ${this.index}`); + switch (state) { + case CommonConstants.AV_PLAYER_INITIALIZED_STATE: + this.hasPrepared = true + this.avPlayer.surfaceId = this.surfaceId; + this.avPlayer.prepare().then(() => { + }, (err: BusinessError) => { + console.error(`Invoke prepare failed, code is ${err.code}, message is ${err.message}`); + this.avPlayer?.reset(); + }); + break; + case CommonConstants.AV_PLAYER_PREPARED_STATE: + this.avPlayer.videoScaleType = media.VideoScaleType.VIDEO_SCALE_TYPE_FIT_CROP; + // 视频循环播放 + this.avPlayer.loop = true; + if (this.firstInit && this.currentIndex === this.index && this.index === 0) { + // 首次播放需要待宽高获取成功后再播放,防止有视图的缩放效果出现 + const interval = setInterval(() => { + this.avPlayer?.play(); + clearInterval(interval) + }, 200) + this.firstInit = false + } + this.duration = AVPlayerUtil.getInstance().formatTime(this.avPlayer.duration) + AppStorage.setOrCreate(CommonConstants.AV_PLAYER_TOTAL_TIME, this.duration); + break; + case CommonConstants.AV_PLAYER_COMPLETED_STATE: + this.avPlayer.stop(); + break; + case CommonConstants.AV_PLAYER_ERROR_STATE: + this.avPlayer.reset() + break; + default: + console.info('AVPlayer state unknown'); + break; + } + } + /** + * 时间刷新回调 + */ + private onTimeUpdateFunction: (updateTime: number) => void = (updateTime: number) => { + this.currentTime = AVPlayerUtil.getInstance().formatTime(updateTime) + AppStorage.setOrCreate(CommonConstants.AV_PLAYER_CURRENT_TIME, this.currentTime); + this.currentProgress = updateTime / this.avPlayer!.duration * CommonConstants.PROGRESS_HUNDRED + AppStorage.setOrCreate(CommonConstants.AV_PLAYER_PROGRESS, this.currentProgress); + } + private videoSizeChange: (width: number, height: number) => void = (width: number, height: number) => { + // 单位像素 + this.oriSurfaceWidth = width + this.oriSurfaceHeight = height + if (this.currentIndex === this.index) { + hilog.info(DOMAIN, LOG_TAG, + `AVPlayer videoSizeChange width = ${this.oriSurfaceWidth}, height = ${this.oriSurfaceHeight}`); + this.changePortraitVideo(); + } + } + + private async changePortraitVideo() { + try { + this.disableSwipe = false + if (this.oriSurfaceWidth > this.oriSurfaceHeight) { + // 横向视频且特地设备才展示全屏播放按钮 + let widthBp: WidthBreakpoint = this.getUIContext().getWindowWidthBreakpoint(); + let heightBp: HeightBreakpoint = this.getUIContext().getWindowHeightBreakpoint(); + const breakPoint = BreakpointRangeDatabase.getInstance().getBreakpointRange(widthBp, heightBp); + this.showFullScreenBtn = breakPoint === (BreakpointRange.Small_Portrait || BreakpointRange.Medium_Landscape) + } + // 退出全屏状态 + adaptiveRotation?.setOrientationNotFullScreen({ + width: this.oriSurfaceWidth as number, + height: this.oriSurfaceHeight as number + }) + hilog.debug(DOMAIN, LOG_TAG, `changePortraitVideo windowSize = ${JSON.stringify(this.windowSize)}`) + hilog.info(DOMAIN, LOG_TAG, + `changePortraitVideo index = ${this.index}, oriSurfaceWidth = ${this.oriSurfaceWidth}, oriSurfaceHeight = ${this.oriSurfaceHeight}`); + console.debug(`getImmersionInfo.index = ${this.index}`) + const immersionInfo = adaptiveImmersion + .getImmersionInfo({ width: this.oriSurfaceWidth as number, height: this.oriSurfaceHeight as number }, + this.bottomTabHeight); + // 获取裁剪视频信息 + if (immersionInfo) { + hilog.info(DOMAIN, LOG_TAG, `changePortraitVideo immersionInfo = ${JSON.stringify(immersionInfo)}`); + this.surfaceWidth = immersionInfo.videoSize.width; + this.surfaceHeight = immersionInfo.videoSize.height; + this.videoPosition = immersionInfo.videoPosition; + } + } catch (err) { + hilog.error(DOMAIN, LOG_TAG, `changePortraitVideo error = ${JSON.stringify(err)}`); + } + } + + /** + * 进入横屏全屏 + */ + private async setOrientationFullScreen() { + try { + this.disableSwipe = true + const oriWidth = this.oriSurfaceWidth as number + const oriHeight = this.oriSurfaceHeight as number + // 调用库API进入全屏 + adaptiveRotation?.setOrientationFullScreen({ + width: oriWidth, height: oriHeight + }); + this.showFullScreenBtn = false + // 根据宽高比进行等比例计算,防止视频横屏后失真 + hilog.debug(DOMAIN, LOG_TAG, `setOrientationFullScreen windowSize = ${JSON.stringify(this.windowSize)}`) + if (oriWidth > oriHeight) { + this.surfaceHeight = this.windowSize.height; + // 通过宽高比计算出宽度 + this.surfaceWidth = this.surfaceHeight * oriWidth / oriHeight + let positionX = 0 + if (this.windowSize.width > this.surfaceWidth) { + // 视频宽度比窗口宽度小,需要通过position控制显示位置,居中处理 + positionX = (this.windowSize.width - this.surfaceWidth) / 2 + } + this.videoPosition = { x: positionX, y: 0 }; + } + } catch (err) { + hilog.error(DOMAIN, LOG_TAG, `setOrientationFullScreen error = ${JSON.stringify(err)}`); + } + } + + /** + * 使用布局切换通知器 + */ + private callback = (data: string) => { + if (this.currentIndex === this.index) { + // 表示当前在前台的监听才处理 + this.currentScreen = data + if (data === ScreenMode.FULL_SCREEN) { + // 当需要进入横向布局时的ui布局切换逻辑 (如横向视频进入全屏播放时) + hilog.info(DOMAIN, LOG_TAG, `screen callback fullScreen`) + this.hideTab = true + this.setOrientationFullScreen(); + } else { + // 当需要进入竖向布局时的ui布局切换逻辑 (如横向视频退出全屏时) + hilog.info(DOMAIN, LOG_TAG, `screen callback notFullScreen`) + this.hideTab = false + this.changePortraitVideo(); + } + } + } +} \ No newline at end of file diff --git a/features/adaptive_video_component/src/main/ets/components/CustomAdaptiveVideoComponent.ets b/features/adaptive_video_component/src/main/ets/components/CustomAdaptiveVideoComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..2aba2df6c0b59bb88b35ff8fc736dd9945074736 --- /dev/null +++ b/features/adaptive_video_component/src/main/ets/components/CustomAdaptiveVideoComponent.ets @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2025 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 { + AdaptiveRotation, + AdaptiveVideoComponent, + AdaptiveVideoController, + AdaptiveVideoListener, + AdaptiveVideoSetting, + ScreenMode, + ScreenModeNotifier, + ViewMode +} from '@hadss/adaptive_video'; +import { common } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { CommonConstants } from 'base'; +import { AVPlayerUtil } from '../utils/AVPlayerUtil'; +import { PCVideoControl } from './PCVideoControl'; +import { VideoControl } from './VideoControl'; +import { media } from '@kit.MediaKit'; + +const LOG_TAG = 'CustomAdaptiveVideoComponent' +const DOMAIN = 0x0005; +const adaptiveRotation = AdaptiveRotation.getInstance(); +const screenModeNotifier: ScreenModeNotifier = ScreenModeNotifier.getInstance(); + +@Component +export struct CustomAdaptiveVideoComponent { + // 当前正在播放的视频的index + @Prop curIndex: number = 0; + // 当前进度条是否滑动 + @State isSliderMoving: boolean = false; + index: number = 0 + fdPath: string = '' + /** + * 是否隐藏TAB栏,主要控制进入全屏状态后竖屏部分UI信息不展示 + */ + @Consume hideTab: boolean + /** + * 是否隐藏控制栏 + */ + @State hideControl: boolean = true + @Link disableSwipe: boolean + /** + * 是否展示高阶组件自带的进度条 + * 竖屏展示,全屏播放场景不展示 + */ + @State isProgressBarShow: boolean = true + /** + * 是否是2in1设备 + */ + @Consume is2in1Device: boolean + @State viewMode?: ViewMode = ViewMode.SWIPER + @State svgFillColor: ResourceColor = Color.White + @State featureIconWidth: number = 30 + private context = getContext() as common.UIAbilityContext; + // 实例化一个adaptiveVideoController对象,用于控制当前播放视频播放/暂停等行为。 + private adaptiveVideoController?: AdaptiveVideoController = new AdaptiveVideoController(this.context); + // 实例化adaptiveVideoListener对象,用于监听视频状态/事件。 + private adaptiveVideoListener?: AdaptiveVideoListener = new AdaptiveVideoListener(); + private currentVideoSize: Size = { width: 0, height: 0 } + /** + * 底部TAB栏高度 + */ + private bottomTabHeight: number = 0 + private bottomRectHeight: number = 0 + + aboutToAppear(): void { + this.bottomTabHeight = AppStorage.get(CommonConstants.BOTTOM_TAB_HEIGHT) ?? 56 + this.bottomRectHeight = AppStorage.get(CommonConstants.NAVIGATION_INDICATOR_HEIGHT) ?? 0 + this.adaptiveVideoListener?.on('videoSizeChange', (videoSize: Size) => { + hilog.debug(DOMAIN, LOG_TAG, `videoSizeChange videoSize = ${JSON.stringify(videoSize)}`) + this.currentVideoSize = videoSize + adaptiveRotation?.setOrientationNotFullScreen({ + width: this.currentVideoSize.width, + height: this.currentVideoSize.height + }) + }) + this.adaptiveVideoListener?.on('stateChange', (state: media.AVPlayerState) => { + hilog.debug(DOMAIN, LOG_TAG, `index is ${this.index}, stateChange state = ${state}`) + if (state === CommonConstants.AV_PLAYER_PLAYING_STATE) { + this.currentVideoSize = { + width: this.adaptiveVideoController?.avPlayer?.width ?? 0, + height: this.adaptiveVideoController?.avPlayer?.height ?? 0 + } + } + }) + this.adaptiveVideoListener?.on('timeUpdate', (updateTime: number) => { + const currentTime = AVPlayerUtil.getInstance().formatTime(updateTime) + AppStorage.setOrCreate(CommonConstants.AV_PLAYER_CURRENT_TIME, currentTime); + const duration = this.adaptiveVideoController?.avPlayer?.duration ?? 0 + AppStorage.setOrCreate(CommonConstants.AV_PLAYER_TOTAL_TIME, + AVPlayerUtil.getInstance().formatTime(duration)); + const currentProgress = + updateTime / duration * CommonConstants.PROGRESS_HUNDRED + AppStorage.setOrCreate(CommonConstants.AV_PLAYER_PROGRESS, currentProgress); + }) + const context = getContext() as common.UIAbilityContext + adaptiveRotation.init(context) + screenModeNotifier.init(context) + // 注册屏幕模式监听器 + screenModeNotifier.onScreenModeChange(this.callback); + this.isProgressBarShow = !this.is2in1Device + } + + build() { + RelativeContainer() { + Stack() { + // 额外套一层确保视频是居中的 + Column() { + AdaptiveVideoComponent({ + dataSource: this.context.resourceManager.getRawFdSync(this.fdPath), + adaptiveVideoController: this.adaptiveVideoController, + adaptiveVideoListener: this.adaptiveVideoListener, + isAutoPlay: true, + isLoop: true, + viewMode: this.viewMode, + curIndex: this.curIndex, + index: this.index, + bottomTabHeight: this.bottomTabHeight, + isFullScreenIconShow: true, + adaptiveVideoSetting: this.adaptiveVideoSetting, + isProgressBarShow: this.isProgressBarShow + }) + } + .width('100%') + .height('100%') + } + .width('100%') + .height('100%') + .alignSelf(ItemAlign.Center) + .align(Alignment.Center) + .alignContent(Alignment.Center) + + Column({ space: 20 }) { + Image($r('app.media.menu_icon_user_avatar')).width(44) + this.showFeatureIcon($r('app.media.menu_icon_like'), '1557', true) + this.showFeatureIcon($r('app.media.menu_icon_comment'), '566') + this.showFeatureIcon($r('app.media.menu_icon_share'), '147') + Image($r('app.media.menu_icon_music_cover')).width(32) + } + .id('featureIcons') + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .margin({ bottom: this.is2in1Device ? 0 : this.bottomTabHeight + this.bottomRectHeight }) + .padding({ bottom: this.is2in1Device ? 84 : 40, right: this.is2in1Device ? 32 : 16 }) + .visibility(this.hideTab ? Visibility.None : Visibility.Visible) + + if (this.is2in1Device) { + Column() { + Row({ space: 4 }) { + Text() + .width(82) + .height(16) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .borderRadius(16) + Text() + .width(55) + .height(14) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .borderRadius(14) + } + + Text() + .width(669) + .height(14) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .margin({ top: 16 }) + .borderRadius(14) + } + .id('colorBlocks') + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + left: { anchor: '__container__', align: HorizontalAlign.Start } + }) + .padding({ bottom: 84, left: 32 }) + .visibility(this.hideTab ? Visibility.None : Visibility.Visible) + .alignItems(HorizontalAlign.Start) + + PCVideoControl() + .id('pc_control') + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + left: { anchor: '__container__', align: HorizontalAlign.Start } + }) + } else { + Column() { + Row({ space: 4 }) { + Text() + .width(82) + .height(16) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .borderRadius(16) + Text() + .width(55) + .height(14) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .borderRadius(14) + } + + Text() + .width(240) + .height(14) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .margin({ top: 11 }) + .borderRadius(14) + + Text() + .width(240) + .height(14) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .margin({ top: 13 }) + .borderRadius(14) + + Text() + .width(93) + .height(14) + .backgroundColor($r("app.color.color_white_seventy_percent")) + .margin({ top: 16 }) + .borderRadius(14) + } + .id('colorBlocks') + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + left: { anchor: '__container__', align: HorizontalAlign.Start } + }) + .margin({ bottom: this.bottomTabHeight + this.bottomRectHeight }) + .padding({ bottom: 40, left: 16 }) + .visibility(this.hideTab ? Visibility.None : Visibility.Visible) + .width('auto') + .height('auto') + .alignItems(HorizontalAlign.Start) + + + if (this.hideTab) { + if (!this.hideControl) { + Row({ space: 8 }) { + Image($r('app.media.fullscreen_back')) + .width(40) + .onClick(() => { + this.hideTab = false + this.hideControl = false + adaptiveRotation?.setOrientationNotFullScreen({ + width: this.currentVideoSize.width, + height: this.currentVideoSize.height + }) + this.viewMode = ViewMode.SWIPER + }) + Text($r('app.string.title')) + .fontSize(16) + .lineHeight(21) + .fontColor(Color.White) + } + .margin({ + left: 16, + right: 16, + top: 8, + bottom: 8 + }) + + VideoControl() + .id('video_control') + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + left: { anchor: '__container__', align: HorizontalAlign.Start }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .margin({ bottom: this.bottomRectHeight, left: 16, right: 16 }) + } + } + } + } + } + + @Builder + showFeatureIcon(src: ResourceStr, content?: ResourceStr, changeFillColor?: boolean) { + Column() { + Image(src) + .width(changeFillColor ? this.featureIconWidth : 30) + .fillColor(changeFillColor ? this.svgFillColor : Color.White) + if (content) { + Text(content) + .fontSize(14) + .lineHeight(19) + .fontColor(Color.White) + .margin({ top: 5 }) + } + } + } + + aboutToDisappear(): void { + // 注销屏幕模式监听器 + screenModeNotifier.offScreenModeChange(this.callback); + } + + private adaptiveVideoSetting?: AdaptiveVideoSetting = { + interactionEventSetting: { + onFullScreenIconClick: () => { + hilog.debug(DOMAIN, LOG_TAG, `setOrientationFullScreen width = ${JSON.stringify(this.currentVideoSize)}`) + this.hideTab = true + adaptiveRotation.setOrientationFullScreen({ + width: this.currentVideoSize.width, height: this.currentVideoSize.height + }) + this.viewMode = ViewMode.FULL_SCREEN + }, + onSingleClick: () => { + // 单击视频 + if (this.hideTab && this.hideControl) { + // 隐藏TAB栏并且隐藏控制栏的场景,表示横屏未展示控制栏的状态 + this.hideControl = false + } else { + // 单击视频此时需要暂停/播放视频 + hilog.info(DOMAIN, LOG_TAG, `Video showCtrol SingleClick`) + const currentState = this.adaptiveVideoController?.avPlayer?.state + if (currentState === 'playing') { + this.adaptiveVideoController?.pause() + } else if (currentState === 'paused') { + this.adaptiveVideoController?.play() + } + } + }, + onDoubleClick: () => { + // 双击将关注图标变红,同时有个动画效果 + animateTo({ + duration: 3000, + curve: Curve.EaseOut, + playMode: PlayMode.Normal, + onFinish: () => { + this.featureIconWidth = 30 + } + }, () => { + this.svgFillColor = Color.Red + this.featureIconWidth = 40 + }) + } + }, + progressBarSetting: { + selectedColor: Color.White, + isTimeTextShow: false + } + } + /** + * 使用布局切换通知器 + */ + private callback = (data: string) => { + if (this.curIndex === this.index) { + if (data === ScreenMode.FULL_SCREEN) { + // 当需要进入横向布局时的ui布局切换逻辑 (如横向视频进入全屏播放时) + hilog.info(DOMAIN, LOG_TAG, `screen callback fullScreen`) + this.disableSwipe = true + this.isProgressBarShow = false + this.hideTab = true + this.hideControl = true + adaptiveRotation.setOrientationFullScreen({ + width: this.currentVideoSize.width, height: this.currentVideoSize.height + }) + this.viewMode = ViewMode.FULL_SCREEN + } else { + // 当需要进入竖向布局时的ui布局切换逻辑 (如横向视频退出全屏时) + hilog.info(DOMAIN, LOG_TAG, `screen callback notFullScreen`) + this.disableSwipe = false + this.isProgressBarShow = true + this.hideTab = false + this.hideControl = false + adaptiveRotation?.setOrientationNotFullScreen({ + width: this.currentVideoSize.width, + height: this.currentVideoSize.height + }) + this.viewMode = ViewMode.SWIPER + } + } + } +} diff --git a/features/adaptive_video_component/src/main/ets/components/Home.ets b/features/adaptive_video_component/src/main/ets/components/Home.ets new file mode 100644 index 0000000000000000000000000000000000000000..c8add9cc68862e7bfedb7fdc4f1fd9b70d7a74cf --- /dev/null +++ b/features/adaptive_video_component/src/main/ets/components/Home.ets @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2025 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 { CommonConstants } from 'base'; +import { Recommend } from './Recommend'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { deviceInfo } from '@kit.BasicServicesKit'; + +/** + * TAB为图片的索引 + */ +const TAB_ICON_INDEX = 2 +const LOG_TAG = 'Home' +const DOMAIN = 0x0001; + +/** + * 主页面 + */ +@Component +export struct Home { + @State selTabIndex: number = 0 + @StorageProp(CommonConstants.NAVIGATION_INDICATOR_HEIGHT) bottomRectHeight: number = 0 + @Provide hideTab: boolean = false + /** + * 下TAB栏是否沉浸 + */ + @Provide isBottomTabImmersive: boolean = false + /** + * 页面堆栈 + */ + @Consume('pathStack') pageStack: NavPathStack; + /** + * 是否是2in1设备 + */ + @Provide is2in1Device: boolean = false + /** + * 底部TAB数据 + */ + private tabData: ResourceStr[] = [$r('app.string.bottom_tab_title_index'), $r('app.string.bottom_tab_title_friend'), + $r('app.string.bottom_tab_title_friend'), $r('app.string.bottom_tab_title_message'), + $r('app.string.bottom_tab_title_mine')] + private controller: TabsController = new TabsController(); + /** + * 判断是自适应沉浸和旋转规则库还是高阶视频组件库 + */ + private pageIndex: number = 0 + + aboutToAppear(): void { + this.pageIndex = this.pageStack.getParamByName('Home')[0] as number + hilog.debug(DOMAIN, LOG_TAG, `Home pageIndex = ${this.pageIndex}`) + this.is2in1Device = deviceInfo.deviceType === '2in1' + } + + // 自定义页签 + @Builder + Tab(tabIndex: number, tabTitle: ResourceStr, tabIcon?: ResourceStr) { + if (tabIcon) { + Row() { + Image(tabIcon) + .width(30) + .height(30) + } + .height(56) + .layoutWeight(1) + .justifyContent(FlexAlign.Center) + } else { + Text(tabTitle) + .fontSize(16) + .lineHeight(24) + .fontWeight(500) + .fontColor(this.selTabIndex === tabIndex ? Color.White : $r("app.color.color_white_forty_percent")) + .layoutWeight(1) + .textAlign(TextAlign.Center) + .height(56) + .onClick(() => { + this.controller.changeIndex(tabIndex); + this.selTabIndex = tabIndex; + }) + } + } + + build() { + NavDestination() { + if (this.is2in1Device) { + Recommend({ pageIndex: this.pageIndex }) + } else { + RelativeContainer() { + Tabs({ barPosition: BarPosition.Start, controller: this.controller }) { + TabContent() { + Recommend({ pageIndex: this.pageIndex }) + } + } + .barHeight(0) + + Row() { + ForEach(this.tabData, (tabTitle: ResourceStr, index: number) => { + this.Tab(index, tabTitle, index === TAB_ICON_INDEX ? $r('app.media.home_tab_bottom_add') : undefined) + }, (item: string) => item) + } + .width('100%') + .alignRules({ + bottom: { 'anchor': '__container__', 'align': VerticalAlign.Bottom } + }) + .onAreaChange((_oldValue: Area, newValue: Area) => { + try { + AppStorage.setOrCreate(CommonConstants.BOTTOM_TAB_HEIGHT, newValue.height as number); + } catch (err) { + hilog.error(DOMAIN, LOG_TAG, `onAreaChange exception = ${JSON.stringify(err)}`) + } + }) + .margin({ bottom: this.bottomRectHeight }) + .visibility(this.hideTab ? Visibility.None : Visibility.Visible) + } + } + } + .hideTitleBar(true) + .backgroundColor(Color.Black) + } +} \ No newline at end of file diff --git a/features/adaptive_video_component/src/main/ets/components/PCVideoControl.ets b/features/adaptive_video_component/src/main/ets/components/PCVideoControl.ets new file mode 100644 index 0000000000000000000000000000000000000000..9bb4d173b737b12811f3a9f0d19b0a7f80bcb2ae --- /dev/null +++ b/features/adaptive_video_component/src/main/ets/components/PCVideoControl.ets @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025 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 { CommonConstants } from 'base' + +/** + * PC视频播放进度条 + */ +@Component +export struct PCVideoControl { + @StorageProp(CommonConstants.AV_PLAYER_CURRENT_TIME) currentTime: string = '' + @StorageProp(CommonConstants.AV_PLAYER_TOTAL_TIME) totalTime: string = '' + @StorageProp(CommonConstants.AV_PLAYER_PROGRESS) currentProgress: number = 0 + + build() { + Column() { + Column() { + Progress({ value: this.currentProgress, type: ProgressType.Linear }) + .color(Color.White) + .height(4) + } + .height(24) + .justifyContent(FlexAlign.Center) + + Row() { + Image($r('app.media.pc_status')).width(12).height(13).margin({ right: 4 }) + Text() { + Span(this.currentTime) + .fontColor(Color.White) + Span(` / `) + .fontColor($r("app.color.color_white_forty_percent")) + Span(this.totalTime) + .fontColor($r("app.color.color_white_forty_percent")) + } + .fontSize(9) + .lineHeight(12) + + Blank() + + Row({ space: 16 }) { + Text($r('app.string.speed')) + .fontColor(Color.White) + .fontSize(14) + .lineHeight(19) + Image($r('app.media.volume')) + .width(24) + .height(24) + Image($r('app.media.pc_full_screen')) + .width(24) + .height(24) + } + .width('auto') + } + .width('100%') + .padding({ top: 4, bottom: 24 }) + .backgroundColor(Color.Black) + } + .padding({ left: 32, right: 32 }) + .alignItems(HorizontalAlign.Start) + .width('100%') + } +} \ No newline at end of file diff --git a/features/adaptive_video_component/src/main/ets/components/Recommend.ets b/features/adaptive_video_component/src/main/ets/components/Recommend.ets new file mode 100644 index 0000000000000000000000000000000000000000..b32982194ee75da0af56f88813520693b4d305a5 --- /dev/null +++ b/features/adaptive_video_component/src/main/ets/components/Recommend.ets @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2025 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 { AdaptiveRotation, ScreenModeNotifier } from '@hadss/adaptive_video'; +import { CommonConstants } from 'base'; +import { VideoDataSource } from 'base/src/main/ets/model/VideoDataSource'; +import { common } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { CustomAdaptiveVideoComponent } from './CustomAdaptiveVideoComponent'; +import { AdaptiveAVPlayer } from './AdaptiveAVPlayer'; +import { window } from '@kit.ArkUI'; + +/** + * 推荐TAB索引 + */ +const TAB_RECOMMEND_INDEX = 4 +const LOG_TAG = 'Recommend' +const DOMAIN = 0x0002; + +/** + * 滑动到索引为1的场景自动滑动最左边 + */ +const SCROLL_EDGE_START = 1 + + +/** + * 滑动到索引为3的场景自动滑动最右边 + */ +const SCROLL_EDGE_END = 3 + +/** + * 推荐页面 + */ +@Component +export struct Recommend { + /** + * 页面堆栈 + */ + @Consume('pathStack') pageStack: NavPathStack; + @State selTabIndex: number = TAB_RECOMMEND_INDEX + @StorageProp(CommonConstants.SYSTEM_HEIGHT) topRectHeight: number = 0 + @Consume hideTab: boolean + // 当前正在播放的视频的index + @State curIndex: number = 0; + // 状态栏沉浸状态 + @State isStatusBarImmersive: boolean = false; + // 短视频页面底部tab沉浸状态 + @State isBottomTabImmersive: boolean = false; + // 当前进度条是否滑动 + @State isSliderMoving: boolean = false; + scroller: Scroller = new Scroller(); + @State firstInit: boolean = true + /** + * 横屏播放场景禁用滑动切换 + */ + @State disableSwipe: boolean = false + /** + * 是否是2in1设备 + */ + @Consume is2in1Device: boolean + /** + * 搜索栏据右侧位置 + */ + @State searchBarMarginRight: number = 0 + /** + * 判断是自适应沉浸和旋转规则库还是高阶视频组件库 + */ + pageIndex: number = 0 + private controller: TabsController = new TabsController(); + /** + * 顶部TAB数据 + */ + private tabData: ResourceStr[] = [$r('app.string.top_tab_title_group'), $r('app.string.top_tab_title_live'), + $r('app.string.top_tab_title_follow'), $r('app.string.top_tab_title_mall'), + $r('app.string.top_tab_title_recommend')] + private swiperController: SwiperController = new SwiperController(); + private context = getContext() as common.UIAbilityContext; + private windowClass?: window.Window + + // 自定义页签 + @Builder + Tab(tabIndex: number, tabTitle: ResourceStr) { + RelativeContainer() { + Text(tabTitle) + .id('tabTitle') + .fontSize(16) + .lineHeight(21) + .fontWeight(this.selTabIndex === tabIndex ? 500 : 400) + .fontColor(this.selTabIndex === tabIndex ? Color.White : $r("app.color.color_white_forty_percent")) + .textAlign(TextAlign.Center) + .height(56) + .onClick(() => { + this.controller.changeIndex(tabIndex); + this.selTabIndex = tabIndex; + if (tabIndex <= SCROLL_EDGE_START) { + this.scroller.scrollEdge(Edge.Start) + } else if (tabIndex >= SCROLL_EDGE_END) { + this.scroller.scrollEdge(Edge.End) + } + }) + if (this.selTabIndex === tabIndex) { + Divider() + .id('tabDivider') + .strokeWidth(2) + .color(Color.White) + .borderRadius(1) + .alignRules({ + left: { 'anchor': 'tabTitle', 'align': HorizontalAlign.Start }, + right: { 'anchor': 'tabTitle', 'align': HorizontalAlign.End }, + bottom: { 'anchor': 'tabTitle', 'align': VerticalAlign.Bottom }, + }) + .margin({ bottom: 8 }) + } + }.width('auto') + } + + @Builder + SwiperBuilder() { + Stack() { + Swiper(this.swiperController) { + LazyForEach(new VideoDataSource(CommonConstants.VIDEO_SOURCE), (item: string, index: number) => { + if (this.pageIndex === 0) { + AdaptiveAVPlayer({ + currentIndex: this.curIndex, + index: index, + currentSource: this.context?.resourceManager.getRawFdSync(item), + firstInit: this.firstInit, + disableSwipe: this.disableSwipe + }) + } else { + CustomAdaptiveVideoComponent({ + fdPath: item, + curIndex: this.curIndex, + index: index, + disableSwipe: this.disableSwipe + }) + } + }, (item: string) => item) + } + .cachedCount(this.pageIndex === 0 ? this.firstInit ? 0 : 2 : 2) + .width('100%') + .height('100%') + .vertical(true) + .loop(true) + .curve(Curve.Ease) + .duration(300) + .indicator(false) + .onAnimationStart((index: number, targetIndex: number) => { + // 进行预加载资源的操作 + hilog.debug(DOMAIN, LOG_TAG, `Swiper onAnimationStart index = ${index}, targetIndex = ${targetIndex}`) + this.curIndex = targetIndex; + }) + .disableSwipe(this.disableSwipe) + }.align(Alignment.Bottom) + .alignSelf(ItemAlign.End) + } + + build() { + if (this.is2in1Device) { + RelativeContainer() { + this.SwiperBuilder() + + // 顶部工具栏 + TextInput({ placeholder: 'Search' }) + .id('search_input') + .backgroundColor($r("app.color.color_white_ninety_percent")) + .placeholderColor($r("app.color.color_white_forty_percent")) + .alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Top }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .width(328) + .height(40) + .margin({ top: 8, right: this.searchBarMarginRight }) + .focusable(false) + } + } else { + RelativeContainer() { + Tabs({ barPosition: BarPosition.Start, controller: this.controller }) { + TabContent() { + this.SwiperBuilder() + } + } + .barHeight(0) + + RelativeContainer() { + Image($r('app.media.home_tab_top_menu')) + .id('menu') + .width(56) + .height(56) + .padding(8) + .alignRules({ + top: { 'anchor': '__container__', 'align': VerticalAlign.Top }, + left: { 'anchor': '__container__', 'align': HorizontalAlign.Start } + }) + + Image($r('app.media.home_tab_top_search')) + .id('search') + .width(56) + .height(56) + .padding(8) + .alignRules({ + top: { 'anchor': '__container__', 'align': VerticalAlign.Top }, + right: { 'anchor': '__container__', 'align': HorizontalAlign.End } + }) + + Scroll(this.scroller) { + Row({ space: 16 }) { + ForEach(this.tabData, (tabTitle: ResourceStr, index: number) => { + this.Tab(index, tabTitle) + }, (item: string) => item) + }.justifyContent(FlexAlign.Center) + } + .id('top_tabs') + .scrollBar(BarState.Off) + .scrollable(ScrollDirection.Horizontal) + .layoutWeight(1) + .margin({ left: 4, right: 4 }) + .alignRules({ + left: { 'anchor': 'menu', 'align': HorizontalAlign.End }, + right: { 'anchor': 'search', 'align': HorizontalAlign.Start }, + middle: { 'anchor': '__container__', 'align': HorizontalAlign.Center } + }) + .onAppear(() => { + // 默认选中推荐TAB + this.scroller.scrollEdge(Edge.End) + }) + } + .width('100%') + .padding({ left: 16, right: 16 }) + .height(56) + .visibility(this.hideTab ? Visibility.None : Visibility.Visible) + .margin({ top: this.topRectHeight }) + } + } + } + + aboutToDisappear(): void { + // 销毁监听 + AdaptiveRotation.getInstance().reset() + ScreenModeNotifier.getInstance().destroy() + if (canIUse('SystemCapability.Window.SessionManager')) { + this.windowClass?.off('windowTitleButtonRectChange', this.onWindowTitleButtonRectChange) + } + } + + aboutToAppear(): void { + this.windowClass = this.context.windowStage.getMainWindowSync(); + if (canIUse('SystemCapability.Window.SessionManager')) { + this.searchBarMarginRight = this.windowClass?.getTitleButtonRect().width ?? 140 + this.windowClass?.on('windowTitleButtonRectChange', this.onWindowTitleButtonRectChange) + } + } + + private onWindowTitleButtonRectChange: (titleButtonRect: window.TitleButtonRect) => void = + (titleButtonRect: window.TitleButtonRect) => { + if (canIUse('SystemCapability.Window.SessionManager')) { + this.searchBarMarginRight = titleButtonRect.width + } + } +} \ No newline at end of file diff --git a/features/adaptive_video_component/src/main/ets/components/VideoControl.ets b/features/adaptive_video_component/src/main/ets/components/VideoControl.ets new file mode 100644 index 0000000000000000000000000000000000000000..995b00da2a547e7db02e9b7a44dd098fdbab05bd --- /dev/null +++ b/features/adaptive_video_component/src/main/ets/components/VideoControl.ets @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025 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 { CommonConstants } from 'base' + +/** + * 横屏视频播放时的控制界面 + */ +@Component +export struct VideoControl { + @StorageProp(CommonConstants.AV_PLAYER_CURRENT_TIME) currentTime: string = '' + @StorageProp(CommonConstants.AV_PLAYER_TOTAL_TIME) totalTime: string = '' + @StorageProp(CommonConstants.AV_PLAYER_PROGRESS) currentProgress: number = 0 + + @Builder + showFeatureIcon(src: ResourceStr, content?: ResourceStr) { + Row({ space: 3 }) { + Image(src) + .width(22) + if (content) { + Text(content) + .fontSize(14) + .lineHeight(19) + .fontColor(Color.White) + } + } + } + + build() { + Column() { + Text() { + Span(this.currentTime) + .fontColor(Color.White) + Span(` / `) + .fontColor($r("app.color.color_white_forty_percent")) + Span(this.totalTime) + .fontColor($r("app.color.color_white_forty_percent")) + } + .id('play_time') + .fontSize(9) + .lineHeight(12) + + Progress({ value: this.currentProgress, type: ProgressType.Linear }) + .id('video_progress') + .color(Color.White) + .margin({ top: 10 }) + .height(4) + + Row({ space: 18 }) { + this.showFeatureIcon($r('app.media.menu_icon_like'), '1557') + this.showFeatureIcon($r('app.media.menu_icon_comment'), '566') + this.showFeatureIcon($r('app.media.menu_icon_share'), '147') + } + .id('feature_icons') + .margin({ top: 14 }) + } + .alignItems(HorizontalAlign.Start) + } +} \ No newline at end of file diff --git a/features/adaptive_video_component/src/main/ets/utils/AVPlayerUtil.ets b/features/adaptive_video_component/src/main/ets/utils/AVPlayerUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..ce9dae3dc77b188bb69ed59ba22890ea44c3f7f0 --- /dev/null +++ b/features/adaptive_video_component/src/main/ets/utils/AVPlayerUtil.ets @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2025 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 { CommonConstants } from 'base'; + +export class AVPlayerUtil { + private static instance: AVPlayerUtil; + + private constructor() { + } + + static getInstance(): AVPlayerUtil { + if (!AVPlayerUtil.instance) { + AVPlayerUtil.instance = new AVPlayerUtil() + } + return AVPlayerUtil.instance; + } + + /** + * 格式化显示时间 + * @param duration 时间长度 + * @returns 格式化之后的显示时间 + */ + public formatTime(duration: number): string { + let totalSecond: number = Math.round(duration / CommonConstants.PROGRESS_THOUSAND); + let hourNum: number = Math.floor(totalSecond / CommonConstants.SECOND_IN_HOUR); + let minNum: number = Math.floor((totalSecond % CommonConstants.SECOND_IN_HOUR) / CommonConstants.SECOND_IN_MINUTE); + let secNum: number = (totalSecond % CommonConstants.SECOND_IN_HOUR) % CommonConstants.SECOND_IN_MINUTE; + if (hourNum === 0) { + return this.formatUnitTime(minNum) + CommonConstants.COLON + + this.formatUnitTime(secNum); + } else { + return this.formatUnitTime(hourNum) + CommonConstants.COLON + this.formatUnitTime(minNum) + + CommonConstants.COLON + + this.formatUnitTime(secNum); + } + } + + /** + * 时间补位 + * @param time + * @returns 时间补位(不足的前面补一位0) + */ + public formatUnitTime(time: number): string { + if (time >= CommonConstants.ONE && time < CommonConstants.TEN) { + let zero: string = CommonConstants.TIME_PREFIX; + return zero.concat(time.toString()); + } else if (time >= CommonConstants.ZERO && time < CommonConstants.ONE) { + return CommonConstants.EMPTY_TIME; + } + return time.toString(); + } +} \ No newline at end of file diff --git a/features/adaptive_video_component/src/main/module.json5 b/features/adaptive_video_component/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..d2cad91eca201b8c4c3e810f94ebedb80ba46dfa --- /dev/null +++ b/features/adaptive_video_component/src/main/module.json5 @@ -0,0 +1,12 @@ +{ + "module": { + "name": "adaptive_video_component", + "type": "har", + "deviceTypes": [ + "default", + "tablet", + "2in1", + "car" + ] + } +} \ No newline at end of file diff --git a/features/adaptive_video_component/src/main/resources/base/element/color.json b/features/adaptive_video_component/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..b586ec9c1c3f9c56f8bbf3470df5c76c226350a7 --- /dev/null +++ b/features/adaptive_video_component/src/main/resources/base/element/color.json @@ -0,0 +1,16 @@ +{ + "color": [ + { + "name": "color_white_forty_percent", + "value": "#99FFFFFF" + }, + { + "name": "color_white_ninety_percent", + "value": "#1AFFFFFF" + }, + { + "name": "color_white_seventy_percent", + "value": "#4DFFFFFF" + } + ] +} \ No newline at end of file diff --git a/features/adaptive_video_component/src/main/resources/base/element/float.json b/features/adaptive_video_component/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..33ea22304f9b1485b5f22d811023701b5d4e35b6 --- /dev/null +++ b/features/adaptive_video_component/src/main/resources/base/element/float.json @@ -0,0 +1,8 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + } + ] +} diff --git a/features/adaptive_video_component/src/main/resources/base/element/string.json b/features/adaptive_video_component/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..c0ad39b8d3d2cac49e202b335e17c08f9fa8f948 --- /dev/null +++ b/features/adaptive_video_component/src/main/resources/base/element/string.json @@ -0,0 +1,52 @@ +{ + "string": [ + { + "name": "bottom_tab_title_index", + "value": "首页" + }, + { + "name": "bottom_tab_title_friend", + "value": "朋友" + }, + { + "name": "bottom_tab_title_message", + "value": "消息" + }, + { + "name": "bottom_tab_title_mine", + "value": "我" + }, + { + "name": "top_tab_title_group", + "value": "团购" + }, + { + "name": "top_tab_title_live", + "value": "直播" + }, + { + "name": "top_tab_title_follow", + "value": "关注" + }, + { + "name": "top_tab_title_mall", + "value": "商城" + }, + { + "name": "top_tab_title_recommend", + "value": "推荐" + }, + { + "name": "btn_fullscreen", + "value": "全屏播放" + }, + { + "name": "title", + "value": "标题文字内容" + }, + { + "name": "speed", + "value": "倍速" + } + ] +} diff --git a/features/adaptive_video_component/src/main/resources/base/media/fullscreen_back.png b/features/adaptive_video_component/src/main/resources/base/media/fullscreen_back.png new file mode 100644 index 0000000000000000000000000000000000000000..11d88d8ccef7b8a5bf409422487c14c100dfdfbc Binary files /dev/null and b/features/adaptive_video_component/src/main/resources/base/media/fullscreen_back.png differ diff --git a/features/adaptive_video_component/src/main/resources/base/media/home_tab_bottom_add.svg b/features/adaptive_video_component/src/main/resources/base/media/home_tab_bottom_add.svg new file mode 100644 index 0000000000000000000000000000000000000000..49452f4653f57293c4b5a6e456cd7ca40b245740 --- /dev/null +++ b/features/adaptive_video_component/src/main/resources/base/media/home_tab_bottom_add.svg @@ -0,0 +1,13 @@ + + + Created with Pixso. + + + + + diff --git a/features/adaptive_video_component/src/main/resources/base/media/home_tab_top_menu.png b/features/adaptive_video_component/src/main/resources/base/media/home_tab_top_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..731b9336d44bd8f560a81a18557454e107bb47ae Binary files /dev/null and b/features/adaptive_video_component/src/main/resources/base/media/home_tab_top_menu.png differ diff --git a/features/adaptive_video_component/src/main/resources/base/media/home_tab_top_search.png b/features/adaptive_video_component/src/main/resources/base/media/home_tab_top_search.png new file mode 100644 index 0000000000000000000000000000000000000000..004d986e839056013b9355acff8cb88a7be40078 Binary files /dev/null and b/features/adaptive_video_component/src/main/resources/base/media/home_tab_top_search.png differ diff --git a/features/adaptive_video_component/src/main/resources/base/media/icon_fullscreen.svg b/features/adaptive_video_component/src/main/resources/base/media/icon_fullscreen.svg new file mode 100644 index 0000000000000000000000000000000000000000..c2e9e87244f0a81636ed2ee9c480df78d063135b --- /dev/null +++ b/features/adaptive_video_component/src/main/resources/base/media/icon_fullscreen.svg @@ -0,0 +1,7 @@ + + + Created with Pixso. + + + + diff --git a/features/adaptive_video_component/src/main/resources/base/media/icon_pause.svg b/features/adaptive_video_component/src/main/resources/base/media/icon_pause.svg new file mode 100644 index 0000000000000000000000000000000000000000..b64ab5ca5139e792a4d43041788aaeef329b7d2c --- /dev/null +++ b/features/adaptive_video_component/src/main/resources/base/media/icon_pause.svg @@ -0,0 +1,10 @@ + + + Created with Pixso. + + + + + + + diff --git a/features/adaptive_video_component/src/main/resources/base/media/menu_icon_comment.svg b/features/adaptive_video_component/src/main/resources/base/media/menu_icon_comment.svg new file mode 100644 index 0000000000000000000000000000000000000000..fb7b58fbf9f16c39a05231b81b82ca2c44494657 --- /dev/null +++ b/features/adaptive_video_component/src/main/resources/base/media/menu_icon_comment.svg @@ -0,0 +1,10 @@ + + + Created with Pixso. + + + + diff --git a/features/adaptive_video_component/src/main/resources/base/media/menu_icon_like.svg b/features/adaptive_video_component/src/main/resources/base/media/menu_icon_like.svg new file mode 100644 index 0000000000000000000000000000000000000000..434877957af987c42657f51702ff92f19c1e1672 --- /dev/null +++ b/features/adaptive_video_component/src/main/resources/base/media/menu_icon_like.svg @@ -0,0 +1,10 @@ + + + Created with Pixso. + + + + diff --git a/features/adaptive_video_component/src/main/resources/base/media/menu_icon_music_cover.png b/features/adaptive_video_component/src/main/resources/base/media/menu_icon_music_cover.png new file mode 100644 index 0000000000000000000000000000000000000000..4efe2bc162c10904e42c2857e6862ecdc9ad26f9 Binary files /dev/null and b/features/adaptive_video_component/src/main/resources/base/media/menu_icon_music_cover.png differ diff --git a/features/adaptive_video_component/src/main/resources/base/media/menu_icon_share.svg b/features/adaptive_video_component/src/main/resources/base/media/menu_icon_share.svg new file mode 100644 index 0000000000000000000000000000000000000000..c8dac79bc3519bfdfa34b48e074fe312c7f84f0b --- /dev/null +++ b/features/adaptive_video_component/src/main/resources/base/media/menu_icon_share.svg @@ -0,0 +1,10 @@ + + + Created with Pixso. + + + + diff --git a/features/adaptive_video_component/src/main/resources/base/media/menu_icon_user_avatar.png b/features/adaptive_video_component/src/main/resources/base/media/menu_icon_user_avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..9ab451008fdf18ac931306cebac370fdbdabc1f0 Binary files /dev/null and b/features/adaptive_video_component/src/main/resources/base/media/menu_icon_user_avatar.png differ diff --git a/features/adaptive_video_component/src/main/resources/base/media/pc_full_screen.svg b/features/adaptive_video_component/src/main/resources/base/media/pc_full_screen.svg new file mode 100644 index 0000000000000000000000000000000000000000..3b5b083254a29c503a66af102c9e2786083aac9b --- /dev/null +++ b/features/adaptive_video_component/src/main/resources/base/media/pc_full_screen.svg @@ -0,0 +1,16 @@ + + + Created with Pixso. + + + + + + + + + + + + + diff --git a/features/adaptive_video_component/src/main/resources/base/media/pc_status.svg b/features/adaptive_video_component/src/main/resources/base/media/pc_status.svg new file mode 100644 index 0000000000000000000000000000000000000000..c259f0afc635accf34bef22a107895d1361aa0c1 --- /dev/null +++ b/features/adaptive_video_component/src/main/resources/base/media/pc_status.svg @@ -0,0 +1,7 @@ + + + Created with Pixso. + + + + diff --git a/features/adaptive_video_component/src/main/resources/base/media/search.svg b/features/adaptive_video_component/src/main/resources/base/media/search.svg new file mode 100644 index 0000000000000000000000000000000000000000..d30e50fa6ce66342490e693203a2ecfaad0058c0 --- /dev/null +++ b/features/adaptive_video_component/src/main/resources/base/media/search.svg @@ -0,0 +1,14 @@ + + + Created with Pixso. + + + + + + + + + + + diff --git a/features/adaptive_video_component/src/main/resources/base/media/volume.svg b/features/adaptive_video_component/src/main/resources/base/media/volume.svg new file mode 100644 index 0000000000000000000000000000000000000000..beda18b7cb08a5b357730c12ea1c39d9d18bbdad --- /dev/null +++ b/features/adaptive_video_component/src/main/resources/base/media/volume.svg @@ -0,0 +1,18 @@ + + + Created with Pixso. + + + + + + + + + + + + + + + diff --git a/features/adaptive_video_component/src/main/resources/rawfile/16_9.mp4 b/features/adaptive_video_component/src/main/resources/rawfile/16_9.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..03d158ec308f3fa3d251d9f9343a1ff6bbf391fb Binary files /dev/null and b/features/adaptive_video_component/src/main/resources/rawfile/16_9.mp4 differ diff --git a/features/adaptive_video_component/src/main/resources/rawfile/1_1.mp4 b/features/adaptive_video_component/src/main/resources/rawfile/1_1.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..8d195eed6de909fa025d14c4f161a311c038e65c Binary files /dev/null and b/features/adaptive_video_component/src/main/resources/rawfile/1_1.mp4 differ diff --git a/features/adaptive_video_component/src/main/resources/rawfile/3_4.mp4 b/features/adaptive_video_component/src/main/resources/rawfile/3_4.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..c46d74c0bc693f32f981ac6f775c298ac30322da Binary files /dev/null and b/features/adaptive_video_component/src/main/resources/rawfile/3_4.mp4 differ diff --git a/features/adaptive_video_component/src/main/resources/rawfile/4_3.mp4 b/features/adaptive_video_component/src/main/resources/rawfile/4_3.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..a1532b6e9d2d35a94028680857cad817c57af66c Binary files /dev/null and b/features/adaptive_video_component/src/main/resources/rawfile/4_3.mp4 differ diff --git a/features/adaptive_video_component/src/main/resources/rawfile/9_16_1.mp4 b/features/adaptive_video_component/src/main/resources/rawfile/9_16_1.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..f51ad1f0302bd6a032d5fcd83bdf474d832238ca Binary files /dev/null and b/features/adaptive_video_component/src/main/resources/rawfile/9_16_1.mp4 differ diff --git a/features/adaptive_video_component/src/main/resources/rawfile/9_16_2.mp4 b/features/adaptive_video_component/src/main/resources/rawfile/9_16_2.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..90df5832ab5bc23c5d5169e25b46c1ea13874d57 Binary files /dev/null and b/features/adaptive_video_component/src/main/resources/rawfile/9_16_2.mp4 differ diff --git a/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..a63d34ae5ce5833b3874807e2b8d472687c6c5bf --- /dev/null +++ b/hvigor/hvigor-config.json5 @@ -0,0 +1,22 @@ +{ + "modelVersion": "5.0.4", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/hvigorfile.ts b/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3cb9f1a87a81687554a76283af8df27d8bda775 --- /dev/null +++ b/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/oh-package.json5 b/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..ccb960001f4e931cbeca23e566d44e7b91c3957b --- /dev/null +++ b/oh-package.json5 @@ -0,0 +1,9 @@ +{ + "modelVersion": "5.0.4", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.21" + } +} diff --git a/products/entry/.gitignore b/products/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/products/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/products/entry/build-profile.json5 b/products/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..4d611879c7913fb0610c686e2399258ab3a6dad1 --- /dev/null +++ b/products/entry/build-profile.json5 @@ -0,0 +1,28 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/products/entry/hvigorfile.ts b/products/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6edcd90486dd5a853cf7d34c8647f08414ca7a3 --- /dev/null +++ b/products/entry/hvigorfile.ts @@ -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. */ +} diff --git a/products/entry/obfuscation-rules.txt b/products/entry/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/products/entry/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/products/entry/oh-package.json5 b/products/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..8f62b57c759f9b3da9eea8f3d98c145be886f666 --- /dev/null +++ b/products/entry/oh-package.json5 @@ -0,0 +1,13 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "base": "file:../../commons/base", + 'adaptive_video_component': 'file:../../features/adaptive_video_component' + } +} + diff --git a/products/entry/src/main/ets/entryability/EntryAbility.ets b/products/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..f01f90695c9929fc396200543702471868009c11 --- /dev/null +++ b/products/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025 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, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; +import { BusinessError, deviceInfo } from '@kit.BasicServicesKit'; +import { CommonConstants } from 'base'; + +const DOMAIN = 0x0000; + +export default class EntryAbility extends UIAbility { + private uiContext?: UIContext; + + onCreate(_want: Want, _launchParam: AbilityConstant.LaunchParam): void { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); + return; + } + hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.'); + + // 初始横向、竖向断点获取 + windowStage.getMainWindow().then((windowClass: window.Window) => { + // 设置全屏 + windowClass.setWindowLayoutFullScreen(true) + let deviceTypeInfo: string = deviceInfo.deviceType; + if (deviceTypeInfo === '2in1') { + if (canIUse('SystemCapability.Window.SessionManager')) { + // Set the title bar of the 2in1 device to be invisible after the loadContent() call takes effect. + windowClass.setWindowDecorVisible(false); + windowClass.setWindowDecorHeight(56) + windowClass.setDecorButtonStyle({ colorMode: ConfigurationConstant.ColorMode.COLOR_MODE_DARK }) + } + } + this.uiContext = windowClass.getUIContext(); + let widthBp: WidthBreakpoint = this.uiContext.getWindowWidthBreakpoint(); + let heightBp: HeightBreakpoint = this.uiContext.getWindowHeightBreakpoint(); + AppStorage.setOrCreate('currentWidthBreakpoint', widthBp); + AppStorage.setOrCreate('currentHeightBreakpoint', heightBp); + const windowRect = windowClass.getWindowProperties().windowRect + AppStorage.setOrCreate(CommonConstants.WINDOW_SIZE, { + width: px2vp(windowRect.width), height: px2vp(windowRect.height) + } as window.Size); + // 监听变化,刷新数据 + windowClass.on('windowSizeChange', this.onWindowSizeChange); + // 获取布局避让遮挡的区域 + let type = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR; // 以导航条避让为例 + let avoidArea = windowClass.getWindowAvoidArea(type); + let bottomRectHeight = avoidArea.bottomRect.height; // 获取到导航条区域的高度 + AppStorage.setOrCreate(CommonConstants.NAVIGATION_INDICATOR_HEIGHT, px2vp(bottomRectHeight)); + type = window.AvoidAreaType.TYPE_SYSTEM; // 以状态栏避让为例 + avoidArea = windowClass.getWindowAvoidArea(type); + let topRectHeight = avoidArea.topRect.height; // 获取状态栏区域高度 + AppStorage.setOrCreate(CommonConstants.SYSTEM_HEIGHT, px2vp(topRectHeight)); + // 注册监听函数,动态获取避让区域数据 + windowClass.on('avoidAreaChange', this.onAvoidAreaChange); + }).catch((err: BusinessError) => { + console.error(`Failed to obtain the main window. Cause code: ${err.code}, message: ${err.message}`); + }); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); + } + + /** + * 窗口变化监听函数 + */ + private onWindowSizeChange: (windowSize: window.Size) => void = (windowSize: window.Size) => { + let widthBp: WidthBreakpoint = this.uiContext!.getWindowWidthBreakpoint(); + AppStorage.setOrCreate('currentWidthBreakpoint', widthBp); + let heightBp: HeightBreakpoint = this.uiContext!.getWindowHeightBreakpoint(); + AppStorage.setOrCreate('currentHeightBreakpoint', heightBp); + // 2in1设备全屏非全屏场景只触发onWindowSizeChange,不触发onDisplayChange + const currentWindowSize: window.Size = { + width: px2vp(windowSize.width), height: px2vp(windowSize.height) + }; + AppStorage.setOrCreate(CommonConstants.WINDOW_SIZE, currentWindowSize); + }; + /** + * 区域避让监听函数 + */ + private onAvoidAreaChange: (avoidAreaOptions: window.AvoidAreaOptions) => void = + (avoidAreaOptions: window.AvoidAreaOptions) => { + if (avoidAreaOptions.type === window.AvoidAreaType.TYPE_SYSTEM) { + let topRectHeight = avoidAreaOptions.area.topRect.height; + AppStorage.setOrCreate(CommonConstants.SYSTEM_HEIGHT, px2vp(topRectHeight)); + } else if (avoidAreaOptions.type == window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) { + let bottomRectHeight = avoidAreaOptions.area.bottomRect.height; + AppStorage.setOrCreate(CommonConstants.NAVIGATION_INDICATOR_HEIGHT, px2vp(bottomRectHeight)); + } + }; +} \ No newline at end of file diff --git a/products/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/products/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..f7a1db04e81c8c8f3fc6f50ca18f9a62761b019d --- /dev/null +++ b/products/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 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 '@kit.PerformanceAnalysisKit'; +import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; + +const DOMAIN = 0x0000; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(DOMAIN, 'testTag', 'onBackup ok'); + await Promise.resolve(); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + await Promise.resolve(); + } +} \ No newline at end of file diff --git a/products/entry/src/main/ets/pages/Index.ets b/products/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..5c4e8f5d5fa36d9e657505fff2b235ca94ac748c --- /dev/null +++ b/products/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025 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 { Home } from 'adaptive_video_component'; + +@Entry +@Component +struct Index { + @Provide('pathStack') pathStack: NavPathStack = new NavPathStack(); + + @Builder + PagesMap(name: string) { + if (name == 'Home') { + Home() + } + } + + build() { + Navigation(this.pathStack) { + Column({ space: 20 }) { + Button($r('app.string.index_btn_adaptive_immersion_rotation')) + .width('100%') + .onClick(() => { + console.info(`AVPlayer aboutToAppear click`); + this.pathStack.pushPathByName('Home', 0); + }) + Button($r('app.string.index_btn_high_level_media_components')) + .width('100%') + .onClick(() => { + this.pathStack.pushPathByName('Home', 1); + }) + } + .padding({ left: 20, right: 20 }) + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + } + .mode(NavigationMode.Stack) + .hideToolBar(true) + .hideTitleBar(true) + .hideBackButton(true) + .width('100%') + .height('100%') + .navDestination(this.PagesMap) + .backgroundColor(Color.Black) + } +} \ No newline at end of file diff --git a/products/entry/src/main/module.json5 b/products/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..cee1662010a0c81cbbbe1974ffd3e7e8a89ff24e --- /dev/null +++ b/products/entry/src/main/module.json5 @@ -0,0 +1,54 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone", + "tablet", + "2in1", + "car" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "orientation": "auto_rotation_restricted", + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/products/entry/src/main/resources/base/element/color.json b/products/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/products/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/products/entry/src/main/resources/base/element/float.json b/products/entry/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..33ea22304f9b1485b5f22d811023701b5d4e35b6 --- /dev/null +++ b/products/entry/src/main/resources/base/element/float.json @@ -0,0 +1,8 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + } + ] +} diff --git a/products/entry/src/main/resources/base/element/string.json b/products/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..77e3dd562a3cef207246c8a65847333b55626e99 --- /dev/null +++ b/products/entry/src/main/resources/base/element/string.json @@ -0,0 +1,24 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "短视频场景" + }, + { + "name": "index_btn_adaptive_immersion_rotation", + "value": "基于自适应沉浸和旋转规则库的短视频播放" + }, + { + "name": "index_btn_high_level_media_components", + "value": "基于高阶媒体组件的短视频播放" + } + ] +} \ No newline at end of file diff --git a/products/entry/src/main/resources/base/media/background.png b/products/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/products/entry/src/main/resources/base/media/background.png differ diff --git a/products/entry/src/main/resources/base/media/foreground.png b/products/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97014d3e10e5ff511409c378cd4255713aecd85f Binary files /dev/null and b/products/entry/src/main/resources/base/media/foreground.png differ diff --git a/products/entry/src/main/resources/base/media/layered_image.json b/products/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/products/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/products/entry/src/main/resources/base/media/startIcon.png b/products/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/products/entry/src/main/resources/base/media/startIcon.png differ diff --git a/products/entry/src/main/resources/base/profile/backup_config.json b/products/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/products/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/products/entry/src/main/resources/base/profile/main_pages.json b/products/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..1898d94f58d6128ab712be2c68acc7c98e9ab9ce --- /dev/null +++ b/products/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/products/entry/src/main/resources/dark/element/color.json b/products/entry/src/main/resources/dark/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..79b11c2747aec33e710fd3a7b2b3c94dd9965499 --- /dev/null +++ b/products/entry/src/main/resources/dark/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#000000" + } + ] +} \ No newline at end of file diff --git a/screenshots/screenshots.gif b/screenshots/screenshots.gif new file mode 100644 index 0000000000000000000000000000000000000000..00f6d0108a64b518cbadaf01f390f27df506677a Binary files /dev/null and b/screenshots/screenshots.gif differ