fanmingyou3_wd

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

Showing 100 changed files with 2099 additions and 13 deletions

Too many changes to show.

To preserve performance only 100 of 100+ files are displayed.

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
... ...