fanmingyou3_wd

添加播放器模块,及短视频详情播放模块

Showing 132 changed files with 3102 additions and 26 deletions
export interface AuthorListDTO {
authorName: string;
}
... ...
import { AuthorListDTO } from './AuthorListDTO';
import { FullColumnImgUrlDTO } from './FullColumnImgUrlDTO';
import { ReLInfoDTO } from './ReLInfoDTO';
import { ShareInfoDTO } from './ShareInfoDTO';
import { VideoInfoDTO } from './VideoInfoDTO';
/**
* 接口定义:
* http://192.168.1.3:3300/project/3802/interface/api/200915
*/
export interface ContentDetailDTO {
newsId: string;
newsTitle: string;
newsShortTitle: string;
newsDownTitle: string;
newsBodyTitle: string;
publishTime: string;
appstyle: number;
newsType: number;
newsSummary: string;
newsSource: string;
newsSourceName: string;
newsContent: string;
newsContentBak: string;
newsLinkUrl: string;
bestNoticer: number;
newLinkObject?: any;
newIntroduction: string;
authorList: AuthorListDTO[];
editorName: string;
openAudio: number;
audioList: any[];
hasPopUp?: any;
popUps?: any[];
firstFrameImageUri: string;
reLInfo?: ReLInfoDTO;
fullColumnImgUrls: FullColumnImgUrlDTO[];
shareInfo: ShareInfoDTO;
photoList: any[];
videoInfo: VideoInfoDTO[];
liveInfo?: any;
voteInfo?: any;
rmhInfo?: any;
userInfo?: any;
openLikes: number;
openComment: number;
likesStyle: number;
preCommentFlag: number;
commentDisplay: number;
keyArticle: number;
rmhPlatform: number;
readFlag?: number;
topicInfo?: any;
traceId: string;
itemId: string;
sceneId: string;
subSceneId: string;
activityInfos: any[];
recommendShow: number;
visitorComment: number;
itemTypeCode: string;
menuShow: number;
newsTags: string;
specialColumnId?: any;
specialColumnName: string;
timeline?: any;
traceInfo: string;
viewCount: number;
}
\ No newline at end of file
... ...
export interface FullColumnImgUrlDTO {
format?: any;
height: number;
landscape: number;
size: number;
url: string;
weight: number;
}
... ...
export interface ReLInfoDTO {
channelId: number;
relId: string;
relObjectId: number;
relType: string;
}
... ...
export interface ShareInfoDTO {
shareCoverUrl: string;
shareOpen: number;
sharePosterCoverUrl: string;
sharePosterOpen: number;
shareSummary: string;
shareTitle: string;
shareUrl: string;
}
... ...
export interface VideoInfoDTO {
clarity: number;
resolutionHeight: number;
resolutionWidth: number;
videoDuration: number;
videoLandScape: number;
videoType: number;
videoUrl: string;
}
\ No newline at end of file
... ...
... ... @@ -5,13 +5,13 @@
"name": "default",
"type": "HarmonyOS",
"material": {
"certpath": "C:\\Users\\PC\\.ohos\\config\\auto_debug_sight_harmony_com.wondertek.sight_70086000327424393.cer",
"storePassword": "0000001B4100D63EDF7155D2954BDDEC8F40FA74E710B0D1FF3C0782DE2745F90F1B83D1C1C110398F4559",
"certpath": "C:\\Users\\PC\\.ohos\\config\\auto_debug_sight_harmony_com.wondertek.sight_70086000309521319.cer",
"storePassword": "0000001B264B065AE68D1C7F3C0863A33C083D91E12CC54AA36D44179AA8DBA37EA3C50E7F3692F5EB6F3E",
"keyAlias": "debugKey",
"keyPassword": "0000001B157D6824BE8F21F112459AD1B61654DE9396A50B0CE60898F02F4C95CBF127122F293DD6C80C62",
"profile": "C:\\Users\\PC\\.ohos\\config\\auto_debug_sight_harmony_com.wondertek.sight_70086000327424393.p7b",
"keyPassword": "0000001BFB62A8007F44B0EAAF9CF878A92620ED75A21E53B3740EA60DEBF6543F6E16AA7200542280D746",
"profile": "C:\\Users\\PC\\.ohos\\config\\auto_debug_sight_harmony_com.wondertek.sight_70086000309521319.p7b",
"signAlg": "SHA256withECDSA",
"storeFile": "C:\\Users\\PC\\.ohos\\config\\auto_debug_sight_harmony_com.wondertek.sight_70086000327424393.p12"
"storeFile": "C:\\Users\\PC\\.ohos\\config\\auto_debug_sight_harmony_com.wondertek.sight_70086000309521319.p12"
}
}
],
... ... @@ -133,7 +133,7 @@
]
}
]
}
},
// {
// "name": "wdLayout",
// "srcPath": "./wdLayout",
... ... @@ -145,6 +145,66 @@
// ]
// }
// ]
// }
// },
{
"name": "wdPlayer",
"srcPath": "./wdPlayer",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
},
{
"name": "wdDetailPlayApi",
"srcPath": "./wdDetailPlayApi",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
},
{
"name": "wdDetailPlayShortVideo",
"srcPath": "./wdDetailPlayShortVideo",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
},
{
"name": "wdDetailPlayVod",
"srcPath": "./wdDetailPlayVod",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
},
{
"name": "wdDetailPlayLive",
"srcPath": "./wdDetailPlayLive",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
}
]
}
\ No newline at end of file
... ...
... ... @@ -4,6 +4,7 @@ import UIAbility from '@ohos.app.ability.UIAbility';
import Want from '@ohos.app.ability.Want';
import window from '@ohos.window';
import { registerRouter } from 'wdRouter';
import { WindowModel } from 'wdKit';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
... ... @@ -18,7 +19,16 @@ export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
WindowModel.shared.setWindowStage(windowStage);
// let a = new WindowModel();
// 设置窗口的显示方向属性
WindowModel.shared.setPreferredOrientation(window.Orientation.PORTRAIT)
.then(() => {
hilog.info(0x0000, 'testTag', 'setPreferredOrientation Succeeded');
})
.catch((err: Error) => {
hilog.error(0x0000, 'testTag', `setPreferredOrientation catch, error error.name : ${err.name}, error.message:${err.message}`);
})
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
... ...
{
"code":"0",
"data":[
{
"activityInfos":[
],
"appstyle":13,
"audioList":[
],
"authorList":[
{
"authorName":""
}
],
"bestNoticer":1,
"commentDisplay":1,
"editorName":"韩文鋆",
"firstFrameImageUri":"https://cdnjdout.aikan.pdnews.cn/zhbj-20240127/vod/content/output/f45ef51bb33f4ffd9458f8b386aa3227_opt.png",
"fullColumnImgUrls":[
{
"format":null,
"height":837,
"landscape":1,
"size":1222753,
"url":"https://cdnjdphoto.aikan.pdnews.cn/sjbj-20240127/image/display/efd5771a861f45dd8170da1c3c8c4d04.png",
"weight":1256
}
],
"hasPopUp":null,
"itemId":"",
"itemTypeCode":"",
"keyArticle":0,
"likesStyle":1,
"liveInfo":null,
"menuShow":1,
"newIntroduction":"",
"newLinkObject":null,
"newsBodyTitle":"",
"newsContent":"",
"newsContentBak":"",
"newsDownTitle":"",
"newsId":30013266075,
"newsLinkUrl":"",
"newsShortTitle":"",
"newsSource":"41",
"newsSourceName":"中国铁路微信公号",
"newsSummary":"",
"newsTags":"",
"newsTitle":"旅途平安!这首歌送给即将启程回家的你",
"newsType":1,
"openAudio":1,
"openComment":1,
"openLikes":1,
"photoList":[
],
"popUps":[
],
"preCommentFlag":1,
"publishTime":"2024-01-27 14:18:52",
"reLInfo":{
"channelId":2002,
"relId":"500000301942",
"relObjectId":2002,
"relType":"1"
},
"readFlag":0,
"recommendShow":1,
"rmhInfo":null,
"rmhPlatform":0,
"sceneId":"",
"shareInfo":{
"shareCoverUrl":"https://cdnjdphoto.aikan.pdnews.cn/zhbj-20240127/image/content/6706775f96a346a8a1e7393c325e043d.png?x-oss-process=image/resize,w_200",
"shareOpen":1,
"sharePosterCoverUrl":"https://cdnjdphoto.aikan.pdnews.cn/sjbj-20240127/image/display/efd5771a861f45dd8170da1c3c8c4d04.png",
"sharePosterOpen":1,
"shareSummary":"人民日报,有品质的新闻",
"shareTitle":"旅途平安!这首歌送给即将启程回家的你",
"shareUrl":"https://people.pdnews.cn/vod/rel/500000301942/30013266075"
},
"specialColumnId":null,
"specialColumnName":"",
"subSceneId":"",
"timeline":null,
"topicInfo":null,
"traceId":"",
"traceInfo":"",
"userInfo":null,
"videoInfo":[
{
"clarity":5,
"resolutionHeight":1080,
"resolutionWidth":1920,
"videoDuration":143,
"videoLandScape":1,
"videoType":1,
"videoUrl":"https://cdnjdout.aikan.pdnews.cn/zhbj-20240127/vod/content/output/f45ef51bb33f4ffd9458f8b386aa3227_opt.mp4"
}
],
"viewCount":0,
"voteInfo":null
}
],
"message":"Success",
"meta":null,
"requestId":"",
"success":true,
"timestamp":1706514747211
}
\ No newline at end of file
... ...
... ... @@ -30,6 +30,8 @@ export { Action } from './src/main/ets/bean/programme/Action'
export { Params } from './src/main/ets/bean/programme/Params'
export { Pic } from './src/main/ets/bean/programme/Pic'
export { LabelBean } from './src/main/ets/bean/component/extra/LabelBean';
export { LabelDTO } from './src/main/ets/bean/component/extra/LabelDTO';
... ... @@ -48,6 +50,6 @@ export { NewspaperPositionItemBean } from './src/main/ets/bean/newspaper/Newspap
export { NewspaperShareBean } from './src/main/ets/bean/newspaper/NewspaperShareBean';
export { NewspaperTimeInfoBean} from './src/main/ets/bean/newspaper/NewspaperTimeInfoBean';
export { NewspaperTimeInfoBean } from './src/main/ets/bean/newspaper/NewspaperTimeInfoBean';
export { NewspaperTimeItemBean } from './src/main/ets/bean/newspaper/NewspaperTimeItemBean';
... ...
... ... @@ -6,5 +6,15 @@ export interface Params {
path?: string;
url?: string;
extra?: ExtraDTO; // 跳转时额外需要带的参数:map<String,String> 即仅有一层的json
// 详情页类型
// 1.点播详情页
// 2.直播详情页
// 3.图文详情页
// 4.全民播详情页
// 5.欢喜详情页
// 6.挂件详情页
// 7.沉浸式竖屏详情页
// 8.专辑竖屏详情页
detailPageType?:number; // 详情页类型
}
... ...
import { CompDTO, ContentDTO, DelayTimeEnum } from 'wdBean';
import { BreakpointConstants, CommonConstants } from 'wdConstant';
import { BreakpointConstants } from 'wdConstant';
import { BreakPointType, Logger } from 'wdKit';
import { CompUtils } from '../utils/CompUtils';
import { CarouselLayout01CardView } from './CardView';
import { EmptyComponent } from './EmptyComponent';
... ...
... ... @@ -71,10 +71,19 @@ export struct CarouselLayout01CardView {
.hoverEffect(HoverEffect.Scale)
.onClick((event: ClickEvent) => {
Logger.info(TAG, `BannerComponent onClick event index: ${this.index}`);
// let taskAction: Action = {
// type: 'JUMP_H5_BY_WEB_VIEW',
// params: {
// url: ConfigConstants.DETAIL_URL
// } as Params,
// };
// WDRouterRule.jumpWithAction(taskAction)
let taskAction: Action = {
type: 'JUMP_H5_BY_WEB_VIEW',
type: 'JUMP_DETAIL_PAGE',
params: {
url: ConfigConstants.DETAIL_URL
detailPageType: 7, // 沉浸式竖屏详情页
contentID: '863556812'
} as Params,
};
WDRouterRule.jumpWithAction(taskAction)
... ...
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test
\ No newline at end of file
... ...
export { ContentDetailRequest, ContentDetailRequestParams } from './src/main/ets/request/ContentDetailRequest'
// export { PlaySetting } from './src/main/ets/utils/PlaySetting'
export { PlaySpeedDialog, } from './src/main/ets/view/PlayDialog'
export { PlayError } from './src/main/ets/view/PlayError'
export { devicePLSensorManager } from './src/main/ets/utils/devicePortLandSensor'
... ...
{
"apiType": "stageMode",
"buildOption": {
"arkOptions": {
// "apPath": "./modules.ap" /* Profile used for profile-guided optimization (PGO), a compiler optimization technique to improve app runtime performance. */
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": true,
"files": [
"./obfuscation-rules.txt"
]
}
}
}
},
],
"targets": [
{
"name": "default"
}
]
}
\ No newline at end of file
... ...
import { hspTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: hspTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}
... ...
# 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://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md
# 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
\ No newline at end of file
... ...
{
"name": "wddetailplayapi",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "Index.ets",
"author": "",
"license": "Apache-2.0",
"dependencies": {
"wdBean": "file:../wdBean",
"wdKit": "file:../wdKit",
"wdNetwork": "file:../wdNetwork",
"wdRouter": "file:../wdRouter"
}
}
\ No newline at end of file
... ...
export interface AuthorListDTO {
authorName: string;
}
... ...
import { AuthorListDTO } from './AuthorListDTO';
import { FullColumnImgUrlDTO } from './FullColumnImgUrlDTO';
import { ReLInfoDTO } from './ReLInfoDTO';
import { ShareInfoDTO } from './ShareInfoDTO';
import { VideoInfoDTO } from './VideoInfoDTO';
/**
* 接口定义:
* http://192.168.1.3:3300/project/3802/interface/api/200915
*/
export interface ContentDetailDTO {
newsId: string;
newsTitle: string;
newsShortTitle: string;
newsDownTitle: string;
newsBodyTitle: string;
publishTime: string;
appstyle: number;
newsType: number;
newsSummary: string;
newsSource: string;
newsSourceName: string;
newsContent: string;
newsContentBak: string;
newsLinkUrl: string;
bestNoticer: number;
newLinkObject?: any;
newIntroduction: string;
authorList: AuthorListDTO[];
editorName: string;
openAudio: number;
audioList: any[];
hasPopUp?: any;
popUps?: any[];
firstFrameImageUri: string;
reLInfo?: ReLInfoDTO;
fullColumnImgUrls: FullColumnImgUrlDTO[];
shareInfo: ShareInfoDTO;
photoList: any[];
videoInfo: VideoInfoDTO[];
liveInfo?: any;
voteInfo?: any;
rmhInfo?: any;
userInfo?: any;
openLikes: number;
openComment: number;
likesStyle: number;
preCommentFlag: number;
commentDisplay: number;
keyArticle: number;
rmhPlatform: number;
readFlag?: number;
topicInfo?: any;
traceId: string;
itemId: string;
sceneId: string;
subSceneId: string;
activityInfos: any[];
recommendShow: number;
visitorComment: number;
itemTypeCode: string;
menuShow: number;
newsTags: string;
specialColumnId?: any;
specialColumnName: string;
timeline?: any;
traceInfo: string;
viewCount: number;
}
\ No newline at end of file
... ...
export interface FullColumnImgUrlDTO {
format?: any;
height: number;
landscape: number;
size: number;
url: string;
weight: number;
}
... ...
export interface ReLInfoDTO {
channelId: number;
relId: string;
relObjectId: number;
relType: string;
}
... ...
export interface ShareInfoDTO {
shareCoverUrl: string;
shareOpen: number;
sharePosterCoverUrl: string;
sharePosterOpen: number;
shareSummary: string;
shareTitle: string;
shareUrl: string;
}
... ...
export interface VideoInfoDTO {
clarity: number;
resolutionHeight: number;
resolutionWidth: number;
videoDuration: number;
videoLandScape: number;
videoType: number;
videoUrl: string;
}
\ No newline at end of file
... ...
import { Logger, ResourcesUtils } from 'wdKit';
import { WDHttp } from 'wdNetwork'
import { ContentDetailDTO } from '../bean/ContentDetailDTO'
const TAG = 'ContentDetailRequest';
const mock_switch = true;
export interface ContentDetailRequestParams {
contentId: string
relId: string
relType: string
}
export class ContentDetailRequest {
static getContentDetailDataMock(context: Context): Promise<WDHttp.ResponseDTO<ContentDetailDTO[]>> {
Logger.info(TAG, `getContentDetailDataMock start`);
return ResourcesUtils.getResourcesJson<WDHttp.ResponseDTO<ContentDetailDTO[]>>(context, 'content_detail.json')
}
/**
* 现网-新闻内容详情域名
*/
static readonly HOST2: string = "https://pdapis.pdnews.cn";
/**
* 新闻内容详情【get】接口
*/
static readonly CONTENT_DETAIL_PATH: string = "/api/rmrb-bff-display-zh/content/zh/c/content/detail";
static getContentDetailUrl(contentId: string, relId: string, relType: string) {
let url = ContentDetailRequest.HOST2 + ContentDetailRequest.CONTENT_DETAIL_PATH
url = url + "?&contentId=" + contentId
+ "&relId=" + relId
+ "&relType=" + relType;
return url;
}
static getContentDetail(params: ContentDetailRequestParams): Promise<WDHttp.ResponseDTO<ContentDetailDTO[]>> {
if (mock_switch) {
return ContentDetailRequest.getContentDetailDataMock(getContext());
}
let headers: Record<string, string> = {};
let url = ContentDetailRequest.getContentDetailUrl(params.contentId, params.relId, params.relType)
// let headers: HashMap<string, string> = HttpUrlUtils.getCommonHeaders();
return WDHttp.Request.get<WDHttp.ResponseDTO<ContentDetailDTO[]>>(url, headers)
}
}
\ No newline at end of file
... ...
import sensor from '@ohos.sensor';
import window from '@ohos.window';
import { WindowModel } from 'wdKit'
export class devicePLSensorManager{
public static devicePLSensorOn(targetOrientation:number) {
try {
sensor.off(sensor.SensorId.ACCELEROMETER);
} catch (e) {}
let requestOrientation = -1; // sensor旋转的角度
let num = -1;
try{
// 订阅加速度传感器数据
sensor.on(sensor.SensorId.ACCELEROMETER, (response: sensor.AccelerometerResponse) => {
if(num < 5){
num ++;
return;
} else {
num = -1;
}
let orientation = -1;
let X = -response.x;
let Y = -response.y;
let Z = -response.z;
let magnitude = X * X + Y * Y;
if (magnitude * 4 >= Z * Z) {
let OneEightyOverPi = 57.29577957855;
let angle = Math.atan2(-Y, X) * OneEightyOverPi;
orientation = 90 - Math.round(angle);
while (orientation >= 360) {
orientation -= 360;
}
while (orientation < 0) {
orientation += 360;
}
}
if (orientation == -1) return; // 水平方向不处理
if (orientation > 315 || orientation < 45){
requestOrientation = window.Orientation.PORTRAIT;
}
else if (orientation > 45 && orientation < 135){
requestOrientation = window.Orientation.LANDSCAPE;
}
else if (orientation > 225 && orientation < 315){
requestOrientation = window.Orientation.LANDSCAPE_INVERTED;
}
if(targetOrientation == window.Orientation.PORTRAIT && requestOrientation == window.Orientation.PORTRAIT){
WindowModel.shared.setPreferredOrientation(window.Orientation.AUTO_ROTATION_RESTRICTED);
sensor.off(sensor.SensorId.ACCELEROMETER);
}
if(targetOrientation == window.Orientation.LANDSCAPE && (requestOrientation == window.Orientation.LANDSCAPE || requestOrientation == window.Orientation.LANDSCAPE_INVERTED)){
WindowModel.shared.setPreferredOrientation(window.Orientation.AUTO_ROTATION_RESTRICTED);
sensor.off(sensor.SensorId.ACCELEROMETER);
}
}, { interval: 2000000000 })
} catch (e) {
console.error(`屏幕状态报错:${e}`);
try {
sensor.off(sensor.SensorId.ACCELEROMETER);
} catch (e) {
}
}
}
public static devicePLSensorOff(){
try {
sensor.off(sensor.SensorId.ACCELEROMETER);
} catch (e) {}
}
}
\ No newline at end of file
... ...
import { SpeedBean, PlayerConstants } from 'wdPlayer';
// 倍速Dialog
@Preview
@CustomDialog
export struct PlaySpeedDialog {
@Link playSpeed: number;
@Link @Watch('closeDialog') isFullScreen: boolean
controller: CustomDialogController;
closeDialog(propName: string) {
if (this.isFullScreen == false) {
this.controller.close();
}
}
build() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
List({ space: '10%', initialIndex: 0 }) {
ForEach(PlayerConstants.SPEED_ARRAY, (item: SpeedBean) => {
ListItem() {
Text(item.text)
.width('100%')
.textAlign(TextAlign.Center)
.fontColor(this.playSpeed == item.value ? Color.Blue : Color.White)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.onClick(() => {
this.playSpeed = item.value;
})
}
})
}
.padding(12)
.alignListItem(ListItemAlign.Center)
}
.width('20%')
.height('100%')
.backgroundColor('rgba(0,0,0,0.8)')
}
}
// 码率..
... ...
import router from '@ohos.router';
import window from '@ohos.window';
import { WindowModel } from 'wdKit';
import { devicePLSensorManager } from '../utils/devicePortLandSensor';
@Component
export struct PlayError {
@Consume message: string;
@Consume isFullScreen: boolean;
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Image($r('app.media.ic_back'))
.width($r("app.float.back_image_width"))
.height($r("app.float.back_image_width"))
.position({ x: $r("app.float.back_image_width"), y: $r("app.float.back_image_width") })
.aspectRatio(1)
.onClick(() => {
if (this.isFullScreen) {
this.isFullScreen = false;
WindowModel.shared.setPreferredOrientation(window.Orientation.PORTRAIT);
devicePLSensorManager.devicePLSensorOn(window.Orientation.PORTRAIT);
} else {
router.back();
}
})
Text(this.message)
.width('100%')
.textAlign(TextAlign.Center)
.fontColor(Color.White)
.fontSize($r("app.float.font_size_14"))
.visibility(this.message ? Visibility.Visible : Visibility.None)
}
.width('100%')
.height('100%')
}
}
\ No newline at end of file
... ...
{
"module": {
"name": "wdDetailPlayApi",
"type": "shared",
"description": "$string:shared_desc",
"deviceTypes": [
"phone",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"requestPermissions": [
{
"name":"ohos.permission.ACCELEROMETER"
}
]
}
}
\ No newline at end of file
... ...
{
"color": [
{
"name": "white",
"value": "#FFFFFF"
}
]
}
\ No newline at end of file
... ...
{
"float": [
{
"name": "back_image_width",
"value": "16vp"
},
{
"name": "font_size_14",
"value": "14fp"
}
]
}
\ No newline at end of file
... ...
{
"string": [
{
"name": "shared_desc",
"value": "description"
}
]
}
\ No newline at end of file
... ...
import localUnitTest from './LocalUnit.test';
export default function testsuite() {
localUnitTest();
}
\ No newline at end of file
... ...
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function localUnitTest() {
describe('localUnitTest',() => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
});
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
});
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
});
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
});
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
let a = 'abc';
let b = 'b';
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
});
});
}
\ No newline at end of file
... ...
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test
\ No newline at end of file
... ...
export { DetailPlayLivePage } from './src/main/ets/pages/DetailPlayLivePage'
\ No newline at end of file
... ...
{
"apiType": "stageMode",
"buildOption": {
"arkOptions": {
// "apPath": "./modules.ap" /* Profile used for profile-guided optimization (PGO), a compiler optimization technique to improve app runtime performance. */
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": true,
"files": [
"./obfuscation-rules.txt"
]
}
}
}
},
],
"targets": [
{
"name": "default"
}
]
}
\ No newline at end of file
... ...
import { hspTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: hspTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}
... ...
# 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://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md
# 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
\ No newline at end of file
... ...
{
"name": "wddetailplaylive",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "Index.ets",
"author": "",
"license": "Apache-2.0",
"dependencies": {
}
}
\ No newline at end of file
... ...
@Entry
@Component
export struct DetailPlayLivePage {
@State message: string = 'Detail Play Live Page';
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}
\ No newline at end of file
... ...
{
"module": {
"name": "wdDetailPlayLive",
"type": "shared",
"description": "$string:shared_desc",
"deviceTypes": [
"phone",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"pages": "$profile:main_pages"
}
}
\ No newline at end of file
... ...
{
"color": [
{
"name": "white",
"value": "#FFFFFF"
}
]
}
\ No newline at end of file
... ...
{
"float": [
{
"name": "image_width",
"value": "24vp"
}
]
}
\ No newline at end of file
... ...
{
"string": [
{
"name": "shared_desc",
"value": "description"
}
]
}
\ No newline at end of file
... ...
import localUnitTest from './LocalUnit.test';
export default function testsuite() {
localUnitTest();
}
\ No newline at end of file
... ...
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function localUnitTest() {
describe('localUnitTest',() => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
});
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
});
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
});
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
});
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
let a = 'abc';
let b = 'b';
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
});
});
}
\ No newline at end of file
... ...
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test
\ No newline at end of file
... ...
export { DetailPlayShortVideoPage } from './src/main/ets/pages/DetailPlayShortVideoPage'
\ No newline at end of file
... ...
{
"apiType": "stageMode",
"buildOption": {
"arkOptions": {
// "apPath": "./modules.ap" /* Profile used for profile-guided optimization (PGO), a compiler optimization technique to improve app runtime performance. */
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": true,
"files": [
"./obfuscation-rules.txt"
]
}
}
}
},
],
"targets": [
{
"name": "default"
}
]
}
\ No newline at end of file
... ...
import { hspTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: hspTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}
... ...
# 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://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md
# 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
\ No newline at end of file
... ...
{
"name": "wddetailplayshortvideo",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "Index.ets",
"author": "",
"license": "Apache-2.0",
"dependencies": {
"wdBean": "file:../wdBean",
"wdPlayer": "file:../wdPlayer",
"wdKit": "file:../wdKit",
"wdNetwork": "file:../wdNetwork",
"wdDetailPlayApi": "file:../wdDetailPlayApi",
"wdRouter": "file:../wdRouter"
}
}
\ No newline at end of file
... ...
import router from '@ohos.router';
import mediaquery from '@ohos.mediaquery';
import window from '@ohos.window';
import { Action } from 'wdBean';
import { WindowModel, SPHelper, Logger } from 'wdKit';
import { WDPlayerController, WDPlayerRenderView, PlayerConstants } from 'wdPlayer';
import { devicePLSensorManager } from 'wdDetailPlayApi';
import { PlayControlViewContainer } from '../view/PlayControlViewContainer';
import { PlayerDetailContainer } from '../view/PlayerDetailContainer';
import { PlayViewModel } from '../viewmodel/PlayViewModel';
import { DetailContainer } from '../view/DetailContainer';
const TAG = 'DetailPlayShortVideoPage';
/**
* 详情&短视频播放页面
*/
@Entry
@Component
export struct DetailPlayShortVideoPage {
private contentId?: string = undefined
private playerController: WDPlayerController = new WDPlayerController();
@Watch("urlChanged") @State url?: string = undefined
@Watch('changeContinue') @Provide nextContId?: string = '';
@Watch('getPlayHistory') @Provide curContId?: string = undefined;
@Watch("playVMChanged") @Provide playVM: PlayViewModel = new PlayViewModel();
@Provide isFullScreen: boolean = false;
@Provide canStart?: boolean = false;
@Provide status: number = PlayerConstants.STATUS_START;
@Provide userId: string = '';
@Provide title?: string = undefined
@Provide message?: string = undefined
playVMChanged(name: string) {
this.url = this.playVM.url
this.title = this.playVM.title
this.curContId = this.playVM.contentId
this.nextContId = this.playVM.nextContId
this.canStart = this.playVM.canStart;
this.message = this.playVM.message
}
aboutToAppear() {
let action: Action = router.getParams() as Action
if (action) {
this.contentId = action.params?.contentID
}
// 设置播放地址
// this.url = 'https://media.w3.org/2010/05/sintel/trailer.mp4'
let listener = mediaquery.matchMediaSync('(orientation: landscape)');
listener.on("change", (mediaQueryResult) => {
if (mediaQueryResult.matches) {
console.log("横屏 yes")
this.isFullScreen = true
} else {
this.isFullScreen = false
console.log("横屏 no")
}
WindowModel.shared.setMainWindowFullScreen(this.isFullScreen)
})
}
onPageShow() {
WindowModel.shared.setPreferredOrientation(window.Orientation.AUTO_ROTATION_RESTRICTED);
}
onPageHide() {
WindowModel.shared.setPreferredOrientation(window.Orientation.PORTRAIT);
devicePLSensorManager.devicePLSensorOff();
this.status = PlayerConstants.STATUS_PAUSE;
this.playerController?.pause();
}
@Builder
playerViewContainer() {
// 播放窗口
WDPlayerRenderView({
playerController: this.playerController,
onLoad: async () => {
this.playVM.playWithContentId(this.contentId ?? "846899373")
}
})
.height('100%')
.width('100%')
}
@Builder
playControlViewContainer() {
// 播放窗口控制bar
PlayControlViewContainer({
playerController: this.playerController
})
}
@Builder
detailContainer() {
// DetailTabBarPageComponent({ pageId: this.pageId }).backgroundColor(Color.Black)
DetailContainer()
}
build() {
Row() {
PlayerDetailContainer({ playerView: () => {
this.playerViewContainer()
}, playControlView: () => {
this.playControlViewContainer()
}, detailView: () => {
this.detailContainer()
} })
.height('100%')
.width('100%')
}
.height('100%')
.width('100%')
.backgroundColor(Color.Black)
}
// 续播判断
changeContinue() {
if (this.nextContId) {
this.playerController.continue = () => {
this.playerController?.stop();
this.playVM.playWithContentId(this.nextContId ?? '');
}
return;
}
this.playerController.continue = undefined;
}
urlChanged(name: string) {
if (this.url) {
console.log("url:" + this.url);
this.status = PlayerConstants.STATUS_START;
this.playerController.firstPlay(this.url);
}
}
getPlayHistory() {
SPHelper.default.get('playHistory', '').then((str) => {
let result = str.toString();
let time = 0;
if (result != null && result != "") {
let playHistory: Record<string, Record<string, number>> = JSON.parse(result);
let userData: Record<string, number> = {};
if (this.userId) {
userData = playHistory[this.userId] ?? {};
}
if (this.curContId) {
time = userData?.[this.curContId] ?? 0;
}
}
this.playerController?.setStartTime(time);
}).catch((err: Error) => {
// Error: Inner error. Error code 15500000
Logger.error(TAG, 'catch err:' + JSON.stringify(err));
this.playerController?.setStartTime(0);
});
}
}
\ No newline at end of file
... ...
import { PlayerTitleComment } from './PlayerTitleComment'
/**
* 非全屏状态-(播放页面底部)非播放区域
*/
@Component
export struct DetailContainer {
build() {
PlayerTitleComment()
.width('100%')
}
}
\ No newline at end of file
... ...
import window from '@ohos.window';
import { WindowModel, SPHelper } from 'wdKit';
import { DateFormatUtil, WDPlayerController, PlayerConstants } from 'wdPlayer';
import { devicePLSensorManager, PlayError } from 'wdDetailPlayApi';
import { PlayerProgressBar } from './PlayerProgressBar';
import { PlayerTitle } from './PlayerTitle';
/**
* 播放窗口上层的控制view
*/
@Component
export struct PlayControlViewContainer {
playerController?: WDPlayerController;
@Consume status: number;
@Provide currentTime: string = "00:00";
@Provide totalTime: string = "00:00";
@Provide progressVal: number = 0;
@Provide isShowVolume: boolean = false;
@Provide volumeProgress: number = 1;
@Consume isFullScreen: boolean;
@State isLocked: boolean = false;
@Provide setAuto: number | undefined = undefined;
@Consume canStart: boolean;
@Consume userId: string;
@Consume curContId: string;
@Consume message: string;
// 用于触发拖动手势事件,滑动的最小距离为5vp时拖动手势识别成功。
private panOptionBright: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Vertical });
private panOptionVolume: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Vertical });
aboutToAppear() {
if (this.playerController == null) {
return
}
this.playerController.onTimeUpdate = (position, duration) => {
this.currentTime = DateFormatUtil.secondToTime(position);
this.totalTime = DateFormatUtil.secondToTime(duration);
this.progressVal = Math.floor(position * 100 / duration);
this.setPlayHistory(position);
}
this.playerController.onVolumeUpdate = (volume) => {
this.volumeProgress = volume;
}
}
aboutToDisappear() {
this.playerController?.release();
}
build() {
Stack({ alignContent: Alignment.Start }) {
Stack() {
Column() {
PlayerTitle({ playerController: this.playerController })
.width('100%')
.height(44)
.visibility(this.isFullScreen ? Visibility.Visible : Visibility.None)
Row() {
Column()
.width('50%')
.height('100%')
.gesture(
PanGesture(this.panOptionBright)
.onActionStart((event?: GestureEvent) => {
this.playerController?.onBrightActionStart(event!);
})
.onActionUpdate((event?: GestureEvent) => {
this.playerController?.onBrightActionUpdate(event!);
})
.onActionEnd(() => {
this.playerController?.onActionEnd();
})
)
Column()
.width('50%')
.height('100%')
.gesture(
PanGesture(this.panOptionVolume)
.onActionStart((event?: GestureEvent) => {
this.isShowVolume = true
this.playerController?.onVolumeActionStart(event!);
})
.onActionUpdate((event?: GestureEvent) => {
this.playerController?.onVolumeActionUpdate(event!);
})
.onActionEnd(() => {
setTimeout(() => {
this.isShowVolume = false
}, 500)
this.playerController?.onActionEnd();
})
)
}
.width('100%')
.layoutWeight(1)
PlayerProgressBar({ playerController: this.playerController })
.width('100%')
.height(this.isFullScreen ? 66 : 44)
}
.height('100%')
.width('100%')
.zIndex(1)
.gesture(TapGesture({ count: 2 })
.onAction((event: GestureEvent) => {
let curStatus = (this.status === PlayerConstants.STATUS_START);
this.status = curStatus ? PlayerConstants.STATUS_PAUSE : PlayerConstants.STATUS_START;
this.playerController?.switchPlayOrPause();
}))
Row() {
Image($r('app.media.ic_volume'))
.width(20)
.height(20)
Progress({ value: this.volumeProgress * 100, total: 100, type: ProgressType.Linear })
.width(100)
.height(5)
.zIndex(2)
}
.width(140)
.height(40)
.borderRadius(4)
.backgroundColor("#FFFFFF")
.opacity(0.5)
.justifyContent(FlexAlign.Center)
.visibility(this.isShowVolume ? Visibility.Visible : Visibility.None)
}.visibility(!this.canStart || this.isLocked ? Visibility.None : Visibility.Visible)
Image(this.isLocked ? $r('app.media.ic_lock') : $r('app.media.ic_unlock'))
.width(20)
.height(20)
.margin({ left: 20 })
.borderRadius(4)
.zIndex(2)
.visibility(this.canStart && this.isFullScreen ? Visibility.Visible : Visibility.None)
.gesture(TapGesture()
.onAction((event: GestureEvent) => {
this.isLocked = !this.isLocked;
if (this.isLocked) {
WindowModel.shared.setPreferredOrientation(window.Orientation.LOCKED);
devicePLSensorManager.devicePLSensorOff();
} else {
WindowModel.shared.setPreferredOrientation(window.Orientation.AUTO_ROTATION_RESTRICTED);
}
}))
PlayError()
.width('100%')
.height('100%')
.zIndex(3)// zIndex值越大,显示层级越高,即zIndex值大的组件会覆盖在zIndex值小的组件上方。
.visibility(this.canStart == undefined && this.message != undefined ? Visibility.Visible : Visibility.None)
}
}
setPlayHistory(position: number) {
if (this.userId && this.curContId) {
SPHelper.default.get('playHistory', '').then((str) => {
let result = str.toString();
let playHistory: Record<string, Record<string, number>> = {};
if (result != null && result != "") {
playHistory = JSON.parse(result);
}
let userData = playHistory[this.userId] ?? {};
userData[this.curContId] = position;
playHistory[this.userId] = userData;
SPHelper.default.save('playHistory', JSON.stringify(playHistory));
})
}
}
}
\ No newline at end of file
... ...
/**
* 详情view&播放器view的容器布局组件
*/
@Component
export struct PlayerDetailContainer {
@BuilderParam playerView: () => void
@BuilderParam playControlView: () => void
@BuilderParam detailView: () => void
@Consume isFullScreen: boolean
build() {
RelativeContainer() {
Stack() {
Row() {
this.playerView()
}
.height('100%')
.width('100%')
.zIndex(0)
Row() {
this.playControlView()
}
.height('100%')
.width('100%')
.zIndex(1)
}
.width('100%')
.aspectRatio(this.isFullScreen ? 0 : 16 / 9.0)
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.id('txt_title')
if (!this.isFullScreen) {
Row() {
this.detailView()
}
.width('100%')
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.id('row_bottomView')
}
}
.width('100%')
.height('100%')
}
}
\ No newline at end of file
... ...
import window from '@ohos.window';
import { WindowModel } from 'wdKit';
import { WDPlayerController, PlayerConstants } from 'wdPlayer';
import { devicePLSensorManager } from 'wdDetailPlayApi';
import { PlaySpeedDialog } from 'wdDetailPlayApi';
import { PlayViewModel } from '../viewmodel/PlayViewModel';
/**
* 全屏播放器进度条
*/
@Component
export struct PlayerProgressBar {
private playerController?: WDPlayerController;
@State curContId?: string = undefined;
@State @Watch('switchSpeed') playSpeed: number = 1;
@Consume currentTime: string;
@Consume totalTime: string;
@Consume progressVal: number;
@Consume status: number;
@Consume isFullScreen: boolean;
@Consume nextContId: string;
@Consume @Watch("playVMChanged") playVM: PlayViewModel;
speedDialogController: CustomDialogController = new CustomDialogController({
builder: PlaySpeedDialog({
playSpeed: $playSpeed,
isFullScreen: $isFullScreen
}),
alignment: DialogAlignment.CenterEnd,
customStyle: true
})
switchSpeed() {
this.playerController?.setSpeed(this.playSpeed);
}
switchPause() {
this.status = PlayerConstants.STATUS_PAUSE;
this.playerController?.pause();
}
switchSubId(contId?: string) {
if (contId) {
this.playerController?.stop();
this.playVM.playWithContentId(contId);
}
}
playVMChanged(name: string) {
this.curContId = this.playVM.contentId
}
build() {
Column() {
Row() {
Column() {
Image(this.status === PlayerConstants.STATUS_START ?
$r('app.media.ic_pause') : $r('app.media.ic_play'))
.width($r('app.float.control_image_width'))
.aspectRatio(1)
.onClick(() => {
let curStatus = (this.status === PlayerConstants.STATUS_START);
this.status = curStatus ? PlayerConstants.STATUS_PAUSE : PlayerConstants.STATUS_START;
this.playerController?.switchPlayOrPause();
})
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.width(44)
.height('100%')
Text(this.currentTime)
.fontSize($r("app.float.font_size_14"))
.fontColor(Color.White)
Slider({
value: this.progressVal,
step: 1,
style: SliderStyle.OutSet
})
.blockColor(Color.White)
.trackColor($r('app.color.track_color'))
.selectedColor($r('app.color.index_tab_selected_font_color'))
.trackThickness(1)
.layoutWeight(1)
.onChange((value: number, mode: SliderChangeMode) => {
this.playerController?.setSeekTime(value, mode);
})
Text(this.totalTime)
.fontSize($r("app.float.font_size_14"))
.fontColor(Color.White)
Column() {
Image($r('app.media.ic_fullscreen'))
.width($r('app.float.control_image_width'))
.aspectRatio(1)
.onClick(() => {
this.isFullScreen = !this.isFullScreen;
WindowModel.shared.setPreferredOrientation(window.Orientation.LANDSCAPE);
devicePLSensorManager.devicePLSensorOn(window.Orientation.LANDSCAPE);
})
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.width(44)
.height('100%')
}
.width('100%')
.visibility(this.isFullScreen ? Visibility.None : Visibility.Visible)
Column() {
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
Text(this.currentTime)
.fontSize($r("app.float.font_size_14"))
.fontColor(Color.White)
Slider({
value: this.progressVal,
step: 1
})
.blockColor(Color.White)
.trackColor($r('app.color.track_color'))
.selectedColor($r('app.color.index_tab_selected_font_color'))
.trackThickness(1)
.layoutWeight(1)
.onChange((value: number, mode: SliderChangeMode) => {
this.playerController?.setSeekTime(value, mode);
})
Text(this.totalTime)
.fontSize($r("app.float.font_size_14"))
.fontColor(Color.White)
}
.width('98%')
.height('40%')
.padding({ left: '1%', right: '1%' })
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Start }) {
Row() {
Image(this.status === PlayerConstants.STATUS_START ?
$r('app.media.ic_pause') : $r('app.media.ic_play'))
.width($r('app.float.control_image_width'))
.aspectRatio(1)
.onClick(() => {
let curStatus = (this.status === PlayerConstants.STATUS_START);
this.status = curStatus ? PlayerConstants.STATUS_PAUSE : PlayerConstants.STATUS_START;
this.playerController?.switchPlayOrPause();
})
.margin({ right: '4%' })
Image($r('app.media.ic_next'))
.width($r('app.float.control_image_width'))
.aspectRatio(1)
.margin({ right: '4%' })
.visibility(this.nextContId ? Visibility.Visible : Visibility.None)
.onClick(() => {
this.playerController?.stop();
this.playVM.playWithContentId(this.nextContId);
})
}
.justifyContent(FlexAlign.Start)
.width('50%')
.height('100%')
.padding({ left: '2%' })
Row() {
Text(this.playSpeed == 1 ? '倍速' : PlayerConstants.SPEED_ARRAY[this.playSpeed].text)
.fontSize($r("app.float.font_size_14"))
.fontColor(Color.White)
.margin({ right: '4%' })
.onClick(() => {
this.speedDialogController.open();
})
Column() {
Image($r('app.media.ic_fullscreen'))
.width($r('app.float.control_image_width'))
.aspectRatio(1)
.onClick(() => {
this.isFullScreen = !this.isFullScreen;
WindowModel.shared.setPreferredOrientation(window.Orientation.PORTRAIT);
devicePLSensorManager.devicePLSensorOn(window.Orientation.PORTRAIT);
})
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.width(44)
.height('100%')
.margin({ right: '2%' })
}
.justifyContent(FlexAlign.End)
.width('50%')
.height('100%')
.margin({ right: '2%' })
}
.width('100%')
.height('60%')
}
.width('100%')
.height('100%')
.visibility(this.isFullScreen ? Visibility.Visible : Visibility.None)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
\ No newline at end of file
... ...
import router from '@ohos.router';
import window from '@ohos.window';
import deviceInfo from '@ohos.deviceInfo'
import { WindowModel } from 'wdKit';
import { WDPlayerController } from 'wdPlayer';
import { devicePLSensorManager } from 'wdDetailPlayApi';
@Component
export struct PlayerTitle {
private playerController?: WDPlayerController;
@Consume title?: string
@Consume isFullScreen: boolean;
@State @Watch('watchSpeed') playSpeed: number = 1;
aboutToAppear() {
}
watchSpeed() {
this.playerController?.setSpeed(this.playSpeed);
}
build() {
Row() {
Image($r('app.media.ic_back'))
.width(44)
.padding(13)
.aspectRatio(1)
.onClick(() => {
if (this.isFullScreen) {
if (deviceInfo.deviceType != "phone") {
WindowModel.shared.getWindowSize().then((size) => {
if (size.width > size.height) {
router.back();
} else {
this.isFullScreen = !this.isFullScreen;
WindowModel.shared.setPreferredOrientation(window.Orientation.PORTRAIT);
devicePLSensorManager.devicePLSensorOn(window.Orientation.PORTRAIT);
}
})
} else {
this.isFullScreen = !this.isFullScreen;
WindowModel.shared.setPreferredOrientation(window.Orientation.PORTRAIT);
devicePLSensorManager.devicePLSensorOn(window.Orientation.PORTRAIT);
}
} else {
router.back();
}
})
Text(this.title)
.fontColor(Color.White)
.fontSize('14fp')
.maxLines(2)
.layoutWeight(1)
}.alignItems(VerticalAlign.Center)
}
}
\ No newline at end of file
... ...
import router from '@ohos.router';
import window from '@ohos.window';
import deviceInfo from '@ohos.deviceInfo'
import { WindowModel } from 'wdKit';
import { WDPlayerController } from 'wdPlayer';
import { devicePLSensorManager } from 'wdDetailPlayApi';
@Component
export struct PlayerTitleComment {
private playerController?: WDPlayerController;
@Consume title?: string
@State @Watch('watchSpeed') playSpeed: number = 1;
@Consume isFullScreen: boolean;
@State comment: string = '';
aboutToAppear() {
}
watchSpeed() {
this.playerController?.setSpeed(this.playSpeed);
}
build() {
Column() {
Text(this.title)
.fontColor(Color.White)
.fontSize(14)
.maxLines(1)
Text('查看详情 > ')
.fontColor(Color.White)
.fontSize('14fp')
.maxLines(2)
Divider().height(30)
Row() {
Image($r('app.media.ic_back'))
.width(44)
.aspectRatio(1)
.padding(13)
.margin({ left: 13 })
.onClick(() => {
if (this.isFullScreen) {
if (deviceInfo.deviceType != "phone") {
WindowModel.shared.getWindowSize().then((size) => {
if (size.width > size.height) {
router.back();
} else {
this.isFullScreen = !this.isFullScreen;
WindowModel.shared.setPreferredOrientation(window.Orientation.PORTRAIT);
devicePLSensorManager.devicePLSensorOn(window.Orientation.PORTRAIT);
}
})
} else {
this.isFullScreen = !this.isFullScreen;
WindowModel.shared.setPreferredOrientation(window.Orientation.PORTRAIT);
devicePLSensorManager.devicePLSensorOn(window.Orientation.PORTRAIT);
}
} else {
router.back();
}
})
TextInput({ placeholder: '说两句...', text: this.comment })
.placeholderColor(Color.White)
.placeholderFont({ size: 14 })
.fontColor(Color.White)
.fontSize(14)
.maxLines(1)
.layoutWeight(1)
}.alignItems(VerticalAlign.Center)
}
.width('100%')
.alignItems(HorizontalAlign.Start)
}
}
\ No newline at end of file
... ...
import { BusinessError } from '@ohos.base'
import { Logger } from 'wdKit'
import { WDHttp } from 'wdNetwork'
import { ContentDetailRequest } from 'wdDetailPlayApi'
import { ContentDetailDTO } from 'wdDetailPlayApi/src/main/ets/bean/ContentDetailDTO'
const TAG = 'PlayViewModel';
@Observed
export class PlayViewModel {
contentId: string
relId: string
relType: string
title?: string
url?: string
nextContId?: string
canStart?: boolean
message?: string
constructor() {
// todo:
this.contentId = '30013266075'
this.relId = '500000301942'
this.relType = '1'
// this.getGlobalInfo();
}
playWithContentId(contentId: string) {
this.contentId = contentId;
this.getContentDetailData() // 包括播放地址PlayUrl
}
getContentDetailData() {
ContentDetailRequest.getContentDetail({
contentId: this.contentId,
relId: this.relId,
relType: this.relType
}).then((resDTO: WDHttp.ResponseDTO<ContentDetailDTO[]>) => {
if (!resDTO) {
Logger.error(TAG, 'getContentDetailData then resDTO is empty');
return
}
// Logger.info(TAG, "getNavData then,navResDTO.timeStamp:" + navResDTO.timeStamp);
if (!resDTO.data || resDTO.data.length == 0) {
Logger.error(TAG, `getContentDetailData then body is empty`);
return
}
this.title = resDTO.data[0].newsTitle
this.url = resDTO.data[0].videoInfo[0].videoUrl
this.canStart = true;
this.message = '';
}).catch((err: BusinessError) => {
Logger.error(TAG, `getContentDetailData catch, error.code : ${err.code}, error.message:${err.message}`);
// todo:
// this.title = '旅途平安!这首歌送给即将启程回家的你'
// this.url = 'https://cdnjdout.aikan.pdnews.cn/zhbj-20240127/vod/content/output/f45ef51bb33f4ffd9458f8b386aa3227_opt.mp4'
this.canStart = false;
this.message = '获取播放地址错误';
})
.finally(() => {
Logger.debug(TAG, `getContentDetailData finally`);
})
}
}
\ No newline at end of file
... ...
{
"module": {
"name": "wdDetailPlayShortVideo",
"type": "shared",
"description": "$string:shared_desc",
"deviceTypes": [
"phone",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"pages": "$profile:main_pages"
}
}
\ No newline at end of file
... ...
{
"color": [
{
"name": "index_tab_selected_font_color",
"value": "#007DFF"
},
{
"name": "divider_color",
"value": "#D3D3D3"
},
{
"name": "track_color",
"value": "#888888"
},
{
"name": "speed_text_color",
"value": "#DDDDDD"
},
{
"name": "detail_tab_bar_select",
"value": "#333333"
},
{
"name": "detail_tab_bar_unselect",
"value": "#666666"
}
]
}
\ No newline at end of file
... ...
{
"float": [
{
"name": "control_image_width",
"value": "24vp"
},
{
"name": "font_size_14",
"value": "14fp"
},
{
"name": "title_popup_image_size",
"value": "24vp"
},
{
"name": "back_image_width",
"value": "16vp"
},
{
"name": "title_popup_text_left",
"value": "8vp"
},
{
"name": "title_popup_divider_left",
"value": "47vp"
},
{
"name": "title_popup_font_size",
"value": "16fp"
},
{
"name": "title_dialog_font_size",
"value": "16fp"
},
{
"name": "text_border_radius",
"value": "8vp"
},
{
"name": "main_margin",
"value": "14vp"
},
{
"name": "text_right_bottom_font",
"value": "12vp"
}
]
}
\ No newline at end of file
... ...
{
"string": [
{
"name": "shared_desc",
"value": "description"
}
]
}
\ No newline at end of file
... ...
import localUnitTest from './LocalUnit.test';
export default function testsuite() {
localUnitTest();
}
\ No newline at end of file
... ...
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function localUnitTest() {
describe('localUnitTest',() => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
});
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
});
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
});
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
});
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
let a = 'abc';
let b = 'b';
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
});
});
}
\ No newline at end of file
... ...
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test
\ No newline at end of file
... ...
export { DetailPlayVodPage } from './src/main/ets/pages/DetailPlayVodPage'
\ No newline at end of file
... ...
{
"apiType": "stageMode",
"buildOption": {
"arkOptions": {
// "apPath": "./modules.ap" /* Profile used for profile-guided optimization (PGO), a compiler optimization technique to improve app runtime performance. */
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": true,
"files": [
"./obfuscation-rules.txt"
]
}
}
}
},
],
"targets": [
{
"name": "default"
}
]
}
\ No newline at end of file
... ...
import { hspTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: hspTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}
... ...
# 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://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md
# 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
\ No newline at end of file
... ...
{
"name": "wddetailplayvod",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "Index.ets",
"author": "",
"license": "Apache-2.0",
"dependencies": {
"wdBean": "file:../wdBean",
"wdPlayer": "file:../wdPlayer",
"wdKit": "file:../wdKit",
"wdDetailPlayApi": "file:../wdDetailPlayApi",
"wdRouter": "file:../wdRouter"
}
}
\ No newline at end of file
... ...
@Entry
@Component
export struct DetailPlayVodPage {
@State message: string = 'Detail Play Vod Page';
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}
\ No newline at end of file
... ...
{
"module": {
"name": "wdDetailPlayVod",
"type": "shared",
"description": "$string:shared_desc",
"deviceTypes": [
"phone",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"pages": "$profile:main_pages"
}
}
\ No newline at end of file
... ...
{
"color": [
{
"name": "white",
"value": "#FFFFFF"
}
]
}
\ No newline at end of file
... ...
{
"float": [
{
"name": "control_image_width",
"value": "24vp"
}
]
}
\ No newline at end of file
... ...
{
"string": [
{
"name": "shared_desc",
"value": "description"
}
]
}
\ No newline at end of file
... ...
import localUnitTest from './LocalUnit.test';
export default function testsuite() {
localUnitTest();
}
\ No newline at end of file
... ...
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function localUnitTest() {
describe('localUnitTest',() => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
});
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
});
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
});
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
});
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
let a = 'abc';
let b = 'b';
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
});
});
}
\ No newline at end of file
... ...
... ... @@ -15,3 +15,7 @@ export { LazyDataSource } from './src/main/ets/utils/LazyDataSource'
export { BreakpointSystem, BreakPointType } from './src/main/ets/utils/BreakPointSystem';
export { ToastUtils } from './src/main/ets/utils/ToastUtils';
export { WindowModel } from './src/main/ets/utils/WindowModel'
export { SPHelper } from './src/main/ets/utils/SPHelper'
... ...
import data_preferences from '@ohos.data.preferences';
/*
// SPHelper.default.get("key1", "defValue1").then((value1) => {
// this.message = value1.toString();
// })
// let value2: string = await SPHelper.default.get("key2", "defValue2");
// this.message = result;
*
* 单例模式
*/
export class SPHelper {
private static context: Context;
private static spFilename: string = '__SPHelper';
static init(context: Context) {
SPHelper.context = context;
}
static setSpFilename(spFilename: string) {
SPHelper.spFilename = spFilename;
}
// 静态属性
static default: SPHelper = new SPHelper();
// 私有构造函数
private constructor() {
}
private async getVideoPreferences(): Promise<data_preferences.Preferences> {
let preferences: data_preferences.Preferences = await data_preferences.getPreferences(SPHelper.context, SPHelper.spFilename);
return preferences;
}
private getVideoPreferencesSync(): data_preferences.Preferences {
let options: data_preferences.Options = { name: SPHelper.spFilename };
let preferences: data_preferences.Preferences = data_preferences.getPreferencesSync(SPHelper.context, options);
return preferences;
}
async save(key: string, value: data_preferences.ValueType) {
const preferences: data_preferences.Preferences = await this.getVideoPreferences();
await preferences.put(key, value)
await preferences.flush()
}
saveSync(key: string, value: data_preferences.ValueType) {
const preferences: data_preferences.Preferences = this.getVideoPreferencesSync();
preferences.putSync(key, value)
preferences.flush() // todo:Asynchronously
}
async get(key: string, defValue: data_preferences.ValueType): Promise<data_preferences.ValueType> {
const preferences: data_preferences.Preferences = await this.getVideoPreferences();
return await preferences.get(key, defValue);
}
getSync(key: string, defValue: data_preferences.ValueType): data_preferences.ValueType {
const preferences: data_preferences.Preferences = this.getVideoPreferencesSync();
return preferences.getSync(key, defValue);
}
async has(key: string): Promise<boolean> {
const preferences: data_preferences.Preferences = await this.getVideoPreferences();
return await preferences.has(key);
}
hasSync(key: string): boolean {
const preferences: data_preferences.Preferences = this.getVideoPreferencesSync();
return preferences.hasSync(key);
}
async delete(key: string) {
const preferences: data_preferences.Preferences = await this.getVideoPreferences();
preferences.delete(key).then(async () => {
await preferences.flush();
}).catch((err: Error) => {
// Logger.error(TAG, 'Failed to delete the key. Cause: ' + err);
});
}
deleteSync(key: string) {
const preferences: data_preferences.Preferences = this.getVideoPreferencesSync();
preferences.deleteSync(key)
preferences.flush(); // todo:Asynchronously
}
async clearSync() {
this.getVideoPreferences().then(async (preferences: data_preferences.Preferences) => {
preferences.clearSync()
await preferences.flush()
}).catch((err: Error) => {
// Logger.error(TAG, 'get the preferences failed, Cause: ' + err);
});
}
// clearSync() {
// let preferences: data_preferences.Preferences = this.getVideoPreferencesSync()
// preferences.clearSync()
// preferences.flush()
// }
}
\ No newline at end of file
... ...
import window from '@ohos.window';
import { BusinessError } from '@ohos.base';
import deviceInfo from '@ohos.deviceInfo'
import display from '@ohos.display';
export class Size {
width: number = 0
height: number = 0
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
}
export class WindowModel {
private windowStage?: window.WindowStage;
static shared: WindowModel = new WindowModel()
static TAG = "WindowModel";
setWindowStage(windowStage: window.WindowStage) {
this.windowStage = windowStage;
}
setMainWindowFullScreen(fullScreen: boolean) {
if (deviceInfo.deviceType != "phone") {
return
}
if (this.windowStage === undefined) {
return;
}
this.windowStage.getMainWindow((err, windowClass: window.Window) => {
if (err.code) {
return;
}
windowClass.setWindowLayoutFullScreen(fullScreen, (err) => {
if (err.code) {
return;
}
});
windowClass.setWindowSystemBarEnable(fullScreen ? [] : ['status', 'navigation'], (err) => {
if (err.code) {
return;
}
});
});
}
getWindowSize(): Promise<Size> {
return new Promise((resolve, reject) => {
if (!this.windowStage) {
let dis = display.getDefaultDisplaySync();
reject(new Size(dis.width, dis.height))
return;
}
let rect = this.windowStage.getMainWindowSync().getWindowProperties().windowRect;
resolve(new Size(rect.width, rect.height));
});
}
setWindowKeepScreenOn(isKeepScreenOn: boolean) {
this.windowStage?.getMainWindow((err, windowClass: window.Window) => {
windowClass.setWindowKeepScreenOn(isKeepScreenOn, (err: BusinessError) => {
const errCode: number = err.code;
if (errCode) {
console.error(WindowModel.TAG +'设置屏幕常亮:' + isKeepScreenOn + ',失败: ' + JSON.stringify(err));
return;
}
console.info(WindowModel.TAG +'设置屏幕常亮:' + isKeepScreenOn + ",成功");
})
})
}
/**
* 设置窗口的显示方向属性
* @param orientation
*/
setPreferredOrientation(orientation: window.Orientation): Promise<void> {
return new Promise((resolve, reject) => {
if (!this.windowStage) {
console.error('Failed, the main window is empty');
reject("Failed, the main window is empty")
return;
}
this.windowStage.getMainWindow((err: BusinessError, windowClass: window.Window) => {
if (err.code) {
console.error('Failed to obtain the main window. Cause: ' + err.message);
reject(err)
return;
}
// 2.设置窗口的显示方向属性,使用Promise异步回调。
windowClass.setPreferredOrientation(orientation).then(() => {
console.info('Succeeded in setting the window orientation.');
resolve()
}).catch((err: BusinessError) => {
console.error('Failed to set the window orientation. Cause: ' + err.message);
reject(err)
});
});
})
}
}
... ...
export { ResponseDTO } from './src/main/ets/bean/ResponseDTO'
export { ResponseVO } from './src/main/ets/bean/ResponseVO'
export { ResultCode } from './src/main/ets/bean/ResultCode'
export { WDHttp } from './src/main/ets/http/WDHttp'
\ No newline at end of file
... ...
import http from '@ohos.net.http';
import { BusinessError } from '@ohos.base';
import ArrayList from '@ohos.util.ArrayList';
import {Logger} from 'wdKit';
import { Logger } from 'wdKit';
import { HttpUtils } from '../utils/HttpUtils';
const TAG = 'WDHttp';
... ... @@ -67,9 +67,11 @@ export namespace WDHttp {
// 响应结果
body?: T;
data?: T;
// 请求响应时间戳(unix格式)
timeStamp?: number;
// timestamp?: number;
// 返回当前时间戳,格式:yyyyMMddHHmmss,如20230918102124
... ... @@ -261,7 +263,7 @@ export namespace WDHttp {
} else if (!options.header['Content-Type']) {
options.header['Content-Type'] = 'application/json;charset=utf-8';
}
let commonHeader: Record<string, string | number> = { };
let commonHeader: Record<string, string | number> = {};
Request.globalHeaderProviders.forEach((func) => {
let headers = func();
for (const obj of Object.entries(headers)) {
... ...
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test
\ No newline at end of file
... ...
export { WDPlayerController } from "./src/main/ets/controller/WDPlayerController"
export { WDPlayerRenderView } from "./src/main/ets/pages/WDPlayerRenderView"
export { PlayerConstants } from "./src/main/ets/constants/PlayerConstants"
export { SpeedBean } from "./src/main/ets/bean/SpeedBean"
export { DateFormatUtil } from "./src/main/ets/utils/DateFormatUtil"
... ...
{
"apiType": "stageMode",
"buildOption": {
"arkOptions": {
// "apPath": "./modules.ap" /* Profile used for profile-guided optimization (PGO), a compiler optimization technique to improve app runtime performance. */
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": true,
"files": [
"./obfuscation-rules.txt"
]
}
}
}
},
],
"targets": [
{
"name": "default"
}
]
}
\ No newline at end of file
... ...
import { hspTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: hspTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}
... ...
# 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://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md
# 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
\ No newline at end of file
... ...
{
"name": "wdplayer",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "Index.ets",
"author": "",
"license": "Apache-2.0",
"dependencies": {
}
}
\ No newline at end of file
... ...
export class SpeedBean {
text: string = '';
value: number = 0;
// constructor(text: string, value: number) {
// this.text = text;
// this.value = value;
// }
}
\ No newline at end of file
... ...
import { SpeedBean } from '../bean/SpeedBean';
export class PlayerConstants {
static readonly STATUS_IDLE: number = 0;
static readonly STATUS_START: number = 1;
static readonly STATUS_PAUSE: number = 2;
static readonly STATUS_STOP: number = 3;
static readonly OPERATE_STATE: Array<string> = ['prepared','playing', 'paused', 'completed'];
static readonly SPEED_ARRAY:SpeedBean[] = [
{ text: '0.75X', value: 0 },
{ text: '1.0X', value: 1 },
{ text: '1.25X', value: 2 },
{ text: '1.75X', value: 3 },
{ text: '2.0X', value: 4 }
];
}
export enum AVPlayerStatus {
IDLE = 'idle',
INITIALIZED = 'initialized',
PREPARED = 'prepared',
PLAYING = 'playing',
PAUSED = 'paused',
COMPLETED = 'completed',
STOPPED = 'stopped',
RELEASED = 'released',
ERROR = 'error'
}
export enum Events {
STATE_CHANGE = 'stateChange',
VIDEO_SIZE_CHANGE = "videoSizeChange",
TIME_UPDATE = 'timeUpdate',
ERROR = 'error'
}
\ No newline at end of file
... ...
import media from '@ohos.multimedia.media';
import prompt from '@ohos.promptAction';
import { Logger } from '../utils/Logger';
import { PlayerConstants, AVPlayerStatus, Events } from '../constants/PlayerConstants';
@Observed
export class WDPlayerController {
private initPromise: Promise<void>;
private avPlayer?: media.AVPlayer;
private duration: number = 0;
private status: number = PlayerConstants.STATUS_IDLE;
private loop: boolean = false;
private url: string = '';
private surfaceId: string = ''; // 若播放音频,无需设置surfaceId
private playSpeed: number = 1;
private seekTime: number = 0;
private positionY: number = 0;
private startTime: number = 0;
public onVideoSizeChange?: (width: number, height: number) => void;
public onTimeUpdate?: (position: number, duration: number) => void;
public onVolumeUpdate?: (volume: number) => void;
public continue?: () => void;
constructor() {
Logger.error("初始化")
this.initPromise = this.createAVPlayer();
}
/**
* 创建 videoPlayer对象
*/
private createAVPlayer(): Promise<void> {
return new Promise((resolve, reject) => {
Logger.error("开始创建")
media.createAVPlayer().then((avPlayer) => {
if (avPlayer) {
Logger.error("创建完成1")
this.avPlayer = avPlayer;
this.bindState();
resolve();
} else {
Logger.error("创建完成0")
Logger.error('[PlayVideoModel] createAvPlayer fail!');
reject();
}
});
});
}
/**
* AVPlayer 绑定事件.
*/
private bindState() {
this.avPlayer?.on(Events.STATE_CHANGE, async (state) => {
if (this.avPlayer == null) {
return
}
switch (state) {
case AVPlayerStatus.IDLE:
this.resetProgress();
this.avPlayer.url = this.url;
break;
case AVPlayerStatus.INITIALIZED:
this.avPlayer.surfaceId = this.surfaceId;
this.avPlayer?.prepare();
break;
case AVPlayerStatus.PREPARED:
this.avPlayer.videoScaleType = 0;
if (this.startTime) {
this.setSeekTime(this.startTime, SliderChangeMode.Begin);
}
this.avPlayer.play();
this.duration = this.avPlayer.duration;
break;
case AVPlayerStatus.PLAYING:
this.setBright();
this.status = PlayerConstants.STATUS_START;
this.watchStatus();
break;
case AVPlayerStatus.PAUSED:
this.status = PlayerConstants.STATUS_PAUSE;
this.watchStatus();
break;
case AVPlayerStatus.COMPLETED:
// this.titleThis.playSpeed = 1;
if (this.continue) {
this.continue();
} else {
this.duration = 0;
this.url = this.avPlayer.url || '';
this.avPlayer.reset();
}
break;
case AVPlayerStatus.RELEASED:
this.avPlayer.release();
this.status = PlayerConstants.STATUS_STOP;
this.watchStatus();
Logger.info('[PlayVideoModel] state released called')
break;
default:
Logger.info('[PlayVideoModel] unKnown state: ' + state);
break;
}
});
this.avPlayer?.on(Events.TIME_UPDATE, (time: number) => {
this.initProgress(time);
});
this.avPlayer?.on(Events.ERROR, (error) => {
this.playError(error.message);
})
this.avPlayer?.on(Events.VIDEO_SIZE_CHANGE, (width: number, height: number) => {
if (this.onVideoSizeChange) {
this.onVideoSizeChange(width, height);
}
})
}
public setXComponentController(controller: XComponentController) {
this.surfaceId = controller.getXComponentSurfaceId()
}
async firstPlay(url: string) {
this.url = url;
if (this.avPlayer == null) {
Logger.error("等待")
await this.initPromise;
} else {
if (this.avPlayer.state != AVPlayerStatus.IDLE) {
await this.avPlayer.stop()
await this.avPlayer.release()
this.initPromise = this.createAVPlayer();
await this.initPromise;
}
}
if (this.avPlayer == null) {
return
}
Logger.error("开始播放")
this.avPlayer.url = this.url;
}
async release() {
if (this.avPlayer == null) {
await this.initPromise;
}
if (this.avPlayer == null) {
return
}
this.avPlayer.release();
}
async pause() {
if (this.avPlayer == null) {
await this.initPromise;
}
if (this.avPlayer == null) {
return
}
this.avPlayer.pause();
}
async stop() {
if (this.avPlayer == null) {
await this.initPromise;
}
if (this.avPlayer == null) {
return
}
this.avPlayer.stop();
}
async setLoop() {
if (this.avPlayer == null) {
await this.initPromise;
}
if (this.avPlayer == null) {
return
}
this.loop = !this.loop;
}
async setSpeed(playSpeed: number) {
if (this.avPlayer == null) {
await this.initPromise;
}
if (this.avPlayer == null) {
return
}
if (PlayerConstants.OPERATE_STATE.indexOf(this.avPlayer.state) === -1) {
return;
}
this.playSpeed = playSpeed;
this.avPlayer.setSpeed(this.playSpeed);
}
async switchPlayOrPause() {
if (this.avPlayer == null) {
await this.initPromise;
}
if (this.avPlayer == null) {
return
}
if (this.status === PlayerConstants.STATUS_START) {
this.avPlayer.pause();
} else {
this.avPlayer.play();
}
}
async setSeekTime(value: number, mode: SliderChangeMode) {
if (this.avPlayer == null) {
await this.initPromise;
}
if (this.avPlayer == null) {
return
}
if (mode == SliderChangeMode.Begin) {
this.seekTime = value * 1000;
this.avPlayer.seek(this.seekTime, media.SeekMode.SEEK_PREV_SYNC);
}
if (mode === SliderChangeMode.Moving) {
// this.progressThis.progressVal = value;
// this.progressThis.currentTime = DateFormatUtil.secondToTime(Math.floor(value * this.duration /
// 100 / 1000));
}
if (mode === SliderChangeMode.End) {
this.seekTime = value * this.duration / 100;
this.avPlayer.seek(this.seekTime, media.SeekMode.SEEK_PREV_SYNC);
}
}
setBright() {
// globalThis.windowClass.setWindowBrightness(this.playerThis.bright)
}
getStatus() {
return this.status;
}
initProgress(time: number) {
let nowSeconds = Math.floor(time / 1000);
let totalSeconds = Math.floor(this.duration / 1000);
if (this.onTimeUpdate) {
this.onTimeUpdate(nowSeconds, totalSeconds);
}
}
resetProgress() {
this.seekTime = 0;
// this.progressThis.currentTime = '00:00';
// this.progressThis.progressVal = 0;
}
onVolumeActionStart(event: GestureEvent) {
this.positionY = event.offsetY;
}
onBrightActionStart(event: GestureEvent) {
this.positionY = event.offsetY;
}
volume: number = 1
onVolumeActionUpdate(event: GestureEvent) {
if (!this.avPlayer) {
return
}
if (PlayerConstants.OPERATE_STATE.indexOf(this.avPlayer.state) === -1) {
return;
}
let changeVolume = (event.offsetY - this.positionY) / 300;
let currentVolume = this.volume - changeVolume;
if (currentVolume > 1) {
currentVolume = 1;
}
if (currentVolume <= 0) {
currentVolume = 0;
}
this.volume = currentVolume;
this.avPlayer?.setVolume(this.volume);
this.positionY = event.offsetY;
if (this.onVolumeUpdate) {
this.onVolumeUpdate(this.volume);
}
console.log("volume : " + this.volume)
}
onBrightActionUpdate(event: GestureEvent) {
// if (!this.playerThis.volumeShow) {
// this.playerThis.brightShow = true;
// let changeBright = (this.positionY - event.offsetY) / globalThis.screenHeight;
// let currentBright = this.playerThis.bright + changeBright;
// let brightMinFlag = currentBright <= 0;
// let brightMaxFlag = currentBright > 1;
// this.playerThis.bright = brightMinFlag ? 0 :
// (brightMaxFlag ? 1 : currentBright);
// this.setBright();
// this.positionY = event.offsetY;
// }
}
onActionEnd() {
setTimeout(() => {
this.positionY = 0;
}, 200);
}
watchStatus() {
// if (this.status === PlayConstants.STATUS_START) {
// globalThis.windowClass.setWindowKeepScreenOn(true);
// } else {
// globalThis.windowClass.setWindowKeepScreenOn(false);
// }
}
playError(msg?: string) {
prompt.showToast({
duration: 3000,
message: msg ? msg : "请检查地址输入正确且网络正常"
});
}
setStartTime(time?: number) {
this.startTime = time ?? 0;
}
}
\ No newline at end of file
... ...
import componentUtils from '@ohos.arkui.componentUtils';
import { WDPlayerController } from '../controller/WDPlayerController'
import { WindowModel } from 'wdKit';
class Size {
width: Length = "100%";
height: Length = "100%";
constructor(width: Length, height: Length) {
this.width = width;
this.height = height;
}
}
let insIndex: number = 0;
class MGPlayRenderViewIns {
static intCount: number = 0;
static add() {
MGPlayRenderViewIns.intCount++;
WindowModel.shared.setWindowKeepScreenOn(true);
console.log("add-- +1")
}
static del() {
console.log("add-- -1")
MGPlayRenderViewIns.intCount--;
if (MGPlayRenderViewIns.intCount <= 0) {
WindowModel.shared.setWindowKeepScreenOn(false);
}
}
}
/**
* 播放窗口组件
*/
@Entry
@Component
export struct WDPlayerRenderView {
private playerController?: WDPlayerController;
private xComponentController: XComponentController = new XComponentController();
onLoad?: ((event?: object) => void);
videoWidth: number = 0
videoHeight: number = 0
@State selfSize: Size = new Size('100%', '100%');
private insId: string = "WDPlayRenderView" + insIndex;
aboutToAppear() {
MGPlayRenderViewIns.add();
insIndex++;
if (!this.playerController) {
return
}
this.playerController.onVideoSizeChange = (width: number, height: number) => {
this.videoWidth = width;
this.videoHeight = height;
this.updateLayout()
}
}
aboutToDisappear() {
MGPlayRenderViewIns.del();
}
build() {
Row() {
// 设置为“surface“类型时XComponent组件可以和其他组件一起进行布局和渲染。
XComponent({
id: 'xComponentId112233',
type: 'surface',
controller: this.xComponentController
})
.onLoad(async (event) => {
this.xComponentController.setXComponentSurfaceSize({
surfaceWidth: 1920,
surfaceHeight: 1080
});
this.playerController?.setXComponentController(this.xComponentController)
if (this.onLoad) {
this.onLoad(event)
}
})
.width(this.selfSize.width)
.height(this.selfSize.height)
}
.id(this.insId)
.onAreaChange(() => {
this.updateLayout()
})
.backgroundColor("#000000")
.justifyContent(FlexAlign.Center)
.height('100%')
.width('100%')
}
updateLayout() {
let info = componentUtils.getRectangleById(this.insId);
if (info.size.width > 0 && info.size.height > 0 && this.videoHeight > 0 && this.videoWidth > 0) {
if (info.size.width / info.size.height > this.videoWidth / this.videoHeight) {
let scale = info.size.height / this.videoHeight;
this.selfSize = new Size((this.videoWidth * scale / info.size.width) * 100 + "%", '100%');
} else {
let scale = info.size.width / this.videoWidth;
this.selfSize = new Size('100%', (this.videoHeight * scale / info.size.height) * 100 + "%");
}
}
}
}
\ No newline at end of file
... ...
import { PlayerConstants } from '../constants/PlayerConstants'
export class DateFormatUtil {
static secondToTime(seconds: number) {
let time = '00:00'
let hourUnit = 60 * 60;
let hour = Math.floor(seconds / hourUnit);
let minute = Math.floor((seconds - hour * hourUnit) / 60);
let second = seconds - hour * hourUnit - minute * 60;
if (hour > 0) {
return `${DateFormatUtil.padding(hour.toString())}${':'}${DateFormatUtil.padding(minute.toString())}${':'}${DateFormatUtil.padding(second.toString())}`;
}
if (minute > 0) {
return `${DateFormatUtil.padding(minute.toString())}${':'}${DateFormatUtil.padding(second.toString())}`;
} else {
return `${'00'}${':'}${DateFormatUtil.padding(second.toString())}`;
}
}
static padding(num: string) {
let length = 2;
for (let len = (num.toString()).length; len < length; len = num.length) {
num = `${'0'}${num}`;
}
return num;
}
}
... ...
import hilog from '@ohos.hilog';
/**
* Log level define
*
* @syscap SystemCapability.HiviewDFX.HiLog
*/
enum LogLevel {
DEBUG = 3,
INFO = 4,
WARN = 5,
ERROR = 6,
FATAL = 7
}
/**
* Common log for all features.
*
* @param {string} prefix Identifies the log tag.
*/
export class Logger {
private static domain: number = 0xFF00;
private static prefix: string = 'MiguVideoApp';
private static format: string = `%{public}s, %{public}s`;
/**
* constructor.
*
* @param Prefix Identifies the log tag.
* @param domain Domain Indicates the service domain, which is a hexadecimal integer ranging from 0x0 to 0xFFFFF.
*/
constructor(prefix: string = 'MiguVideoApp', domain: number = 0xFF00) {
Logger.prefix = prefix;
Logger.domain = domain;
}
static debug(...args: string[]) {
hilog.debug(Logger.domain, Logger.prefix, Logger.format, args);
}
static info(...args: string[]) {
hilog.info(Logger.domain, Logger.prefix, Logger.format, args);
}
static warn(...args: string[]) {
hilog.warn(Logger.domain, Logger.prefix, Logger.format, args);
}
static error(...args: string[]) {
hilog.error(Logger.domain, Logger.prefix, Logger.format, args);
}
static fatal(...args: string[]) {
hilog.fatal(Logger.domain, Logger.prefix, Logger.format, args);
}
static isLoggable(level: LogLevel) {
hilog.isLoggable(Logger.domain, Logger.prefix, level);
}
}
export default new Logger('MiguVideoApp', 0xFF00)
\ No newline at end of file
... ...
{
"module": {
"name": "wdPlayer",
"type": "shared",
"description": "$string:shared_desc",
"deviceTypes": [
"phone",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"pages": "$profile:main_pages"
}
}
\ No newline at end of file
... ...
{
"color": [
{
"name": "white",
"value": "#FFFFFF"
}
]
}
\ No newline at end of file
... ...
{
"string": [
{
"name": "shared_desc",
"value": "description"
}
]
}
\ No newline at end of file
... ...
{
"src": [
"pages/WDPlayerRenderView"
]
}
... ...
import localUnitTest from './LocalUnit.test';
export default function testsuite() {
localUnitTest();
}
\ No newline at end of file
... ...
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function localUnitTest() {
describe('localUnitTest',() => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
});
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
});
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
});
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
});
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
let a = 'abc';
let b = 'b';
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
});
});
}
\ No newline at end of file
... ...
import { WDRouterPage } from './WDRouterPage'
import { Action } from 'wdBean'
import ArrayList from '@ohos.util.ArrayList';
import { Action } from 'wdBean'
import { WDRouterPage } from './WDRouterPage'
interface HandleObject {
handle: (action: Action) => (WDRouterPage | undefined)
... ... @@ -8,7 +8,6 @@ interface HandleObject {
}
export class Action2Page {
private static handles: Record<string, ArrayList<HandleObject>> = {};
static register(actionType: string, handle: (action: Action) => (WDRouterPage | undefined), priority: number = 0) {
... ... @@ -49,15 +48,15 @@ export function registerRouter() {
// return WDRouterPage.webLoginPage
// })
// Action2Page.register("JUMP_DETAIL_PAGE", (action) => {
// if (action.params?.pageID == "296ff8a4b07d457cb15b6f9e5f433cc0") {
// return WDRouterPage.tvLivePage
Action2Page.register("JUMP_DETAIL_PAGE", (action: Action) => {
// if (action.params?.detailPageType == 2 || action.params?.detailPageType == 6) {
// return WDRouterPage.detailPlayLivePage
// }
// if (action.params?.detailPageType == 7 || action.params?.detailPageType == 8) {
// return WDRouterPage.shortVideoDetail
// }
// return WDRouterPage.playDetail
// })
if (action.params?.detailPageType == 7 || action.params?.detailPageType == 8) {
return WDRouterPage.detailPlayShortVideoPage
}
return WDRouterPage.detailPlayVodPage
})
Action2Page.register("JUMP_H5_BY_WEB_VIEW", (action) => {
return WDRouterPage.defaultWebPage
... ...
... ... @@ -22,4 +22,10 @@ export class WDRouterPage {
static defaultWebPage = new WDRouterPage("entry", "ets/pages/web/DefaultWebPage");
// 电子报页面
static eNewspaper = new WDRouterPage("entry", "ets/pages/ENewspaper")
// 短视频详情页
static detailPlayShortVideoPage = new WDRouterPage("wdDetailPlayShortVideo", "ets/pages/DetailPlayShortVideoPage");
// 点播详情页
static detailPlayVodPage = new WDRouterPage("wdDetailPlayVod", "ets/pages/DetailPlayVodPage");
// 直播详情页
static detailPlayLivePage = new WDRouterPage("wdDetailPlayLive", "ets/pages/DetailPlayLivePage");
}
... ...