wangyujian_wd

feat:1)直播详情信息直播间信息获取

2)横屏直播逻辑添加
Showing 25 changed files with 645 additions and 35 deletions
1 import { StringUtils } from './StringUtils'; 1 import { StringUtils } from './StringUtils';
2 -import getLunar from './GetLunar' 2 +import getLunar from './GetLunar';
  3 +
3 /** 4 /**
4 * 日期/时间工具 5 * 日期/时间工具
5 */ 6 */
@@ -480,6 +481,34 @@ export class DateTimeUtils { @@ -480,6 +481,34 @@ export class DateTimeUtils {
480 return 0 481 return 0
481 } 482 }
482 } 483 }
  484 + /**
  485 + * 进度值换算
  486 + * @param seconds
  487 + * @returns
  488 + */
  489 + static secondToTime(seconds: number) {
  490 + let time = '00:00'
  491 + let hourUnit = 60 * 60;
  492 + let hour = Math.floor(seconds / hourUnit);
  493 + let minute = Math.floor((seconds - hour * hourUnit) / 60);
  494 + let second = seconds - hour * hourUnit - minute * 60;
  495 + if (hour > 0) {
  496 + return `${DateTimeUtils.padding(hour.toString())}${':'}${DateTimeUtils.padding(minute.toString())}${':'}${DateTimeUtils.padding(second.toString())}`;
  497 + }
  498 + if (minute > 0) {
  499 + return `${DateTimeUtils.padding(minute.toString())}${':'}${DateTimeUtils.padding(second.toString())}`;
  500 + } else {
  501 + return `${'00'}${':'}${DateTimeUtils.padding(second.toString())}`;
  502 + }
  503 + }
  504 +
  505 + static padding(num: string) {
  506 + let length = 2;
  507 + for (let len = (num.toString()).length; len < length; len = num.length) {
  508 + num = `${'0'}${num}`;
  509 + }
  510 + return num;
  511 + }
483 } 512 }
484 513
485 // const dateTimeUtils = new DateTimeUtils() 514 // const dateTimeUtils = new DateTimeUtils()
1 -import HashMap from '@ohos.util.HashMap'  
2 -import { ConfigConstants, SpConstants } from 'wdConstant'  
3 -import { DateTimeUtils, Logger, SPHelper, StringUtils } from 'wdKit' 1 +import HashMap from '@ohos.util.HashMap';
  2 +import { SpConstants } from 'wdConstant';
  3 +import { SPHelper, StringUtils } from 'wdKit';
4 4
5 /** 5 /**
6 * 网络请求业务侧工具类 6 * 网络请求业务侧工具类
@@ -213,6 +213,18 @@ export class HttpUrlUtils { @@ -213,6 +213,18 @@ export class HttpUrlUtils {
213 */ 213 */
214 static readonly LIVE_CHAT_LIST_PATH: string = "/api/live-center-message/zh/a/live/message/chat/list"; 214 static readonly LIVE_CHAT_LIST_PATH: string = "/api/live-center-message/zh/a/live/message/chat/list";
215 /** 215 /**
  216 + * 直播详情-直播数据
  217 + */
  218 + static readonly LIVE_ROOM_DATA_PATH: string = "/api/live-center-message/zh/a/live/room/number/all";
  219 + /**
  220 + * 直播详情-预约直播状态
  221 + */
  222 + static readonly LIVE_APPOINTMENT_STATUS_PATH: string = "/api/live-center-message/zh/c/live/subscribe/query";
  223 + /**
  224 + * 直播详情-预约/取消预约直播
  225 + */
  226 + static readonly LIVE_APPOINTMENT_PATH: string = "/api/live-center-message/zh/c/live/subscribe";
  227 + /**
216 228
217 * 搜索结果 显示tab 数 229 * 搜索结果 显示tab 数
218 */ 230 */
@@ -653,6 +665,21 @@ export class HttpUrlUtils { @@ -653,6 +665,21 @@ export class HttpUrlUtils {
653 return url 665 return url
654 } 666 }
655 667
  668 + static getLiveRoomDataUrl() {
  669 + let url = HttpUrlUtils._hostUrl + HttpUrlUtils.LIVE_ROOM_DATA_PATH
  670 + return url
  671 + }
  672 +
  673 + static getLiveAppointmentStatusUrl() {
  674 + let url = HttpUrlUtils._hostUrl + HttpUrlUtils.LIVE_APPOINTMENT_STATUS_PATH
  675 + return url
  676 + }
  677 +
  678 + static getLiveAppointmentUrl() {
  679 + let url = HttpUrlUtils._hostUrl + HttpUrlUtils.LIVE_APPOINTMENT_PATH
  680 + return url
  681 + }
  682 +
656 static getSearchResultCountDataUrl() { 683 static getSearchResultCountDataUrl() {
657 let url = HttpUrlUtils._hostUrl + HttpUrlUtils.SEARCH_RESULT_COUNT_DATA_PATH 684 let url = HttpUrlUtils._hostUrl + HttpUrlUtils.SEARCH_RESULT_COUNT_DATA_PATH
658 return url 685 return url
@@ -120,3 +120,5 @@ export { appStyleImagesDTO } from './src/main/ets/bean/content/appStyleImagesDTO @@ -120,3 +120,5 @@ export { appStyleImagesDTO } from './src/main/ets/bean/content/appStyleImagesDTO
120 120
121 export { LiveRoomBean,LiveRoomItemBean } from './src/main/ets/bean/live/LiveRoomBean'; 121 export { LiveRoomBean,LiveRoomItemBean } from './src/main/ets/bean/live/LiveRoomBean';
122 122
  123 +export { LiveRoomDataBean } from './src/main/ets/bean/live/LiveRoomDataBean';
  124 +
@@ -158,9 +158,12 @@ export interface LiveDetailsBean { @@ -158,9 +158,12 @@ export interface LiveDetailsBean {
158 */ 158 */
159 liveInfo: LiveInfo 159 liveInfo: LiveInfo
160 fullColumnImgUrls: Array<FullColumnImgUrls> 160 fullColumnImgUrls: Array<FullColumnImgUrls>
161 - vlive: Array<Vlive>  
162 newsTitle: string 161 newsTitle: string
  162 + newsId: string
163 newIntroduction: string 163 newIntroduction: string
  164 + //迁移id
  165 + oldNewsId: string
  166 + reLInfo: ReLInfo
164 } 167 }
165 168
166 export interface LiveInfo { 169 export interface LiveInfo {
@@ -168,6 +171,8 @@ export interface LiveInfo { @@ -168,6 +171,8 @@ export interface LiveInfo {
168 liveState: string 171 liveState: string
169 //2024-04-12 15:00:00 直播开始时间 172 //2024-04-12 15:00:00 直播开始时间
170 planStartTime: string 173 planStartTime: string
  174 + mliveId:string
  175 + vlive: Array<Vlive>
171 } 176 }
172 177
173 export interface FullColumnImgUrls { 178 export interface FullColumnImgUrls {
@@ -179,4 +184,8 @@ export interface Vlive { @@ -179,4 +184,8 @@ export interface Vlive {
179 liveUrl: string 184 liveUrl: string
180 //直播回看地址,多路直播录制文件URL 185 //直播回看地址,多路直播录制文件URL
181 replayUri: string 186 replayUri: string
  187 +}
  188 +
  189 +export interface ReLInfo {
  190 + relId: string
182 } 191 }
  1 +export interface LiveRoomDataBean {
  2 + barrageNum: number,
  3 + likeNum: number,
  4 + liveId: number,
  5 + pv: number,
  6 + subscribeNum: number,
  7 +}
@@ -12,6 +12,7 @@ @@ -12,6 +12,7 @@
12 "wdNetwork": "file:../../commons/wdNetwork", 12 "wdNetwork": "file:../../commons/wdNetwork",
13 "wdKit": "file:../../commons/wdKit", 13 "wdKit": "file:../../commons/wdKit",
14 "wdBean": "file:../../features/wdBean", 14 "wdBean": "file:../../features/wdBean",
15 - "wdConstant": "file:../../commons/wdConstant" 15 + "wdConstant": "file:../../commons/wdConstant",
  16 + "wdDetailPlayApi": "file:../../features/wdDetailPlayApi"
16 } 17 }
17 } 18 }
1 -import { LiveDetailsBean } from 'wdBean/Index'; 1 +import { Action, LiveDetailsBean, LiveRoomDataBean } from 'wdBean/Index';
2 import { LiveViewModel } from '../viewModel/LiveViewModel'; 2 import { LiveViewModel } from '../viewModel/LiveViewModel';
3 import { BottomComponent } from '../widgets/details/BottomComponent'; 3 import { BottomComponent } from '../widgets/details/BottomComponent';
4 import { TabComponent } from '../widgets/details/TabComponent'; 4 import { TabComponent } from '../widgets/details/TabComponent';
5 import { TopPlayComponent } from '../widgets/details/video/TopPlayComponet'; 5 import { TopPlayComponent } from '../widgets/details/video/TopPlayComponet';
  6 +import router from '@ohos.router';
6 7
7 @Entry 8 @Entry
8 @Component 9 @Component
9 export struct DetailPlayLivePage { 10 export struct DetailPlayLivePage {
10 TAG: string = 'DetailPlayLivePage'; 11 TAG: string = 'DetailPlayLivePage';
11 liveViewModel: LiveViewModel = new LiveViewModel() 12 liveViewModel: LiveViewModel = new LiveViewModel()
  13 + @State relId: string = ''
  14 + @State contentId: string = ''
  15 + @State relType: string = ''
12 @Provide liveDetailsBean: LiveDetailsBean = {} as LiveDetailsBean 16 @Provide liveDetailsBean: LiveDetailsBean = {} as LiveDetailsBean
  17 + @Provide liveRoomDataBean: LiveRoomDataBean = {} as LiveRoomDataBean
13 18
14 aboutToAppear(): void { 19 aboutToAppear(): void {
  20 + let par: Action = router.getParams() as Action;
  21 + let params = par?.params;
  22 + this.relId = params?.extra?.relId || '';
  23 + this.relType = params?.extra?.relType || '';
  24 + this.contentId = params?.contentID || '';
15 this.getLiveDetails() 25 this.getLiveDetails()
  26 + this.getLiveRoomData()
16 } 27 }
17 28
18 build() { 29 build() {
19 Column() { 30 Column() {
20 - TopPlayComponent({playUrl:'http://mlive3.video.weibocdn.com/record/alicdn/5018726527666338/index.m3u8'}) 31 + TopPlayComponent()
21 TabComponent() 32 TabComponent()
22 BottomComponent() 33 BottomComponent()
23 } 34 }
@@ -30,8 +41,7 @@ export struct DetailPlayLivePage { @@ -30,8 +41,7 @@ export struct DetailPlayLivePage {
30 } 41 }
31 42
32 getLiveDetails() { 43 getLiveDetails() {
33 - this.liveViewModel.getLiveDetails('20000016333', '500005300349', '1')//2024-04-12 15:00:00  
34 - // this.liveViewModel.getLiveDetails('20000016229', '500005272745', '1')//2024-04-03 05:00:00 44 + this.liveViewModel.getLiveDetails(this.contentId, this.relId, this.relType)
35 .then( 45 .then(
36 (data) => { 46 (data) => {
37 if (data.length > 0) { 47 if (data.length > 0) {
@@ -43,6 +53,17 @@ export struct DetailPlayLivePage { @@ -43,6 +53,17 @@ export struct DetailPlayLivePage {
43 }) 53 })
44 } 54 }
45 55
  56 + getLiveRoomData() {
  57 + this.liveViewModel.getLiveRoomData(this.contentId)
  58 + .then(
  59 + (data) => {
  60 + this.liveRoomDataBean = data
  61 + },
  62 + () => {
  63 +
  64 + })
  65 + }
  66 +
46 aboutToDisappear(): void { 67 aboutToDisappear(): void {
47 68
48 } 69 }
1 import HashMap from '@ohos.util.HashMap'; 1 import HashMap from '@ohos.util.HashMap';
2 import { HttpUrlUtils, ResponseDTO } from 'wdNetwork'; 2 import { HttpUrlUtils, ResponseDTO } from 'wdNetwork';
3 import { HttpRequest } from 'wdNetwork/src/main/ets/http/HttpRequest'; 3 import { HttpRequest } from 'wdNetwork/src/main/ets/http/HttpRequest';
4 -import { Logger } from 'wdKit';  
5 -import { LiveDetailsBean, LiveRoomBean } from 'wdBean/Index'; 4 +import { Logger, ToastUtils } from 'wdKit';
  5 +import { LiveDetailsBean, LiveRoomBean, LiveRoomDataBean } from 'wdBean/Index';
6 6
7 const TAG = 'LiveModel' 7 const TAG = 'LiveModel'
8 8
@@ -107,5 +107,91 @@ export class LiveModel { @@ -107,5 +107,91 @@ export class LiveModel {
107 }) 107 })
108 }) 108 })
109 } 109 }
  110 +
  111 + /**
  112 + * 获取直播数据
  113 + * @param liveId
  114 + * @returns
  115 + */
  116 + getLiveRoomData(liveId: string) {
  117 + let headers: HashMap<string, string> = HttpUrlUtils.getCommonHeaders();
  118 + return new Promise<LiveRoomDataBean>((success, fail) => {
  119 + HttpRequest.get<ResponseDTO<LiveRoomDataBean>>(
  120 + HttpUrlUtils.getLiveRoomDataUrl() + `?liveId=${liveId}`,
  121 + headers).then((data: ResponseDTO<LiveRoomDataBean>) => {
  122 + if (!data || !data.data) {
  123 + fail("数据为空")
  124 + return
  125 + }
  126 + if (data.code != 0) {
  127 + fail(data.message)
  128 + return
  129 + }
  130 + success(data.data)
  131 + }, (error: Error) => {
  132 + fail(error.message)
  133 + Logger.debug(TAG + ":error ", error.toString())
  134 + })
  135 + })
  136 + }
  137 +
  138 + /**
  139 + * 获取直播预约状态
  140 + * @param relationId
  141 + * @param liveId
  142 + * @returns
  143 + */
  144 + getLiveAppointmentStatus(relationId: string, liveId: string) {
  145 + let headers: HashMap<string, string> = HttpUrlUtils.getCommonHeaders();
  146 + return new Promise<boolean>((success, fail) => {
  147 + HttpRequest.get<ResponseDTO<boolean>>(
  148 + HttpUrlUtils.getLiveAppointmentStatusUrl() + `?relationId=${relationId}&liveId=${liveId}`,
  149 + headers).then((data: ResponseDTO<boolean>) => {
  150 + if (!data || !data.data) {
  151 + fail("数据为空")
  152 + return
  153 + }
  154 + if (data.code != 0) {
  155 + fail(data.message)
  156 + return
  157 + }
  158 + success(data.data)
  159 + }, (error: Error) => {
  160 + fail(error.message)
  161 + Logger.debug(TAG + ":error ", error.toString())
  162 + })
  163 + })
  164 + }
  165 +
  166 + /**
  167 + * 直播预约/取消预约
  168 + * @param relationId
  169 + * @param mLiveId
  170 + * @param isSubscribe
  171 + * @returns
  172 + */
  173 + liveAppointment(relationId: string, mLiveId: string, isSubscribe: boolean) {
  174 + let params: Record<string, string> = {};
  175 + params['relationId'] = relationId
  176 + params['liveId'] = mLiveId
  177 + params['isSubscribe'] = `${isSubscribe}`
  178 + let headers: HashMap<string, string> = HttpUrlUtils.getCommonHeaders();
  179 + return new Promise<ResponseDTO<string>>((success, fail) => {
  180 + HttpRequest.post<ResponseDTO<string>>(
  181 + HttpUrlUtils.getLiveAppointmentUrl(),
  182 + params,
  183 + headers).then((data: ResponseDTO<string>) => {
  184 + if (data.code != 0) {
  185 + fail(data.message)
  186 + ToastUtils.shortToast(data.message)
  187 + return
  188 + }
  189 + success(data)
  190 + }, (error: Error) => {
  191 + fail(error.message)
  192 + Logger.debug(TAG + ":error ", error.toString())
  193 + })
  194 + })
  195 + }
110 } 196 }
111 197
1 -import { LiveDetailsBean, LiveRoomBean } from 'wdBean/Index' 1 +import { LiveDetailsBean, LiveRoomBean, LiveRoomDataBean } from 'wdBean/Index'
  2 +import { ResponseDTO } from 'wdNetwork/Index'
2 import { LiveModel } from './LiveModel' 3 import { LiveModel } from './LiveModel'
3 4
4 const TAG = "LiveViewModel" 5 const TAG = "LiveViewModel"
@@ -42,4 +43,37 @@ export class LiveViewModel { @@ -42,4 +43,37 @@ export class LiveViewModel {
42 }) 43 })
43 }) 44 })
44 } 45 }
  46 +
  47 + //直播详情直播间数据
  48 + getLiveRoomData(liveId: string) {
  49 + return new Promise<LiveRoomDataBean>((success, fail) => {
  50 + this.liveModel.getLiveRoomData(liveId).then((data) => {
  51 + success(data)
  52 + }).catch((message: string) => {
  53 + fail(message)
  54 + })
  55 + })
  56 + }
  57 +
  58 + //直播预约状态查询
  59 + getLiveAppointmentStatus(relationId: string, liveId: string) {
  60 + return new Promise<boolean>((success, fail) => {
  61 + this.liveModel.getLiveAppointmentStatus(relationId, liveId).then((data) => {
  62 + success(data)
  63 + }).catch((message: string) => {
  64 + fail(message)
  65 + })
  66 + })
  67 + }
  68 +
  69 + //直播预约/取消预约
  70 + liveAppointment(relationId: string, mLiveId: string, isSubscribe: boolean) {
  71 + return new Promise<ResponseDTO<string>>((success, fail) => {
  72 + this.liveModel.liveAppointment(relationId, mLiveId, isSubscribe).then((data) => {
  73 + success(data)
  74 + }).catch((message: string) => {
  75 + fail(message)
  76 + })
  77 + })
  78 + }
45 } 79 }
1 import font from '@ohos.font' 1 import font from '@ohos.font'
2 import { LiveDetailsBean } from 'wdBean/Index' 2 import { LiveDetailsBean } from 'wdBean/Index'
3 import { DateTimeUtils, StringUtils } from 'wdKit/Index' 3 import { DateTimeUtils, StringUtils } from 'wdKit/Index'
  4 +import { LiveViewModel } from '../../viewModel/LiveViewModel'
4 5
5 @Component 6 @Component
6 export struct LiveCountdownComponent { 7 export struct LiveCountdownComponent {
@@ -13,6 +14,9 @@ export struct LiveCountdownComponent { @@ -13,6 +14,9 @@ export struct LiveCountdownComponent {
13 @State minute: string = '' 14 @State minute: string = ''
14 @State differenceTimeStamp: number = 0 15 @State differenceTimeStamp: number = 0
15 @State isCountDownStart: boolean = false 16 @State isCountDownStart: boolean = false
  17 + //是否预约过直播
  18 + @State isAppointmentLive: boolean = false
  19 + liveViewModel: LiveViewModel = new LiveViewModel()
16 20
17 aboutToAppear(): void { 21 aboutToAppear(): void {
18 //注册字体 22 //注册字体
@@ -107,15 +111,23 @@ export struct LiveCountdownComponent { @@ -107,15 +111,23 @@ export struct LiveCountdownComponent {
107 top: 16 111 top: 16
108 }) 112 })
109 .border({ radius: 4 }) 113 .border({ radius: 4 })
110 - .backgroundColor('#ED2800')  
111 - // .backgroundColor('#CCCCCC') 114 + .backgroundColor(this.isAppointmentLive ? '#CCCCCC' : '#ED2800')
  115 + .onClick(() => {
  116 + if (this.liveDetailsBean && this.liveDetailsBean.liveInfo) {
  117 + this.liveAppointment()
  118 + }
  119 + })
112 } 120 }
113 121
114 calculateDataStatus() { 122 calculateDataStatus() {
  123 + if (!this.liveDetailsBean) {
  124 + return
  125 + }
  126 + this.getLiveAppointmentStatus()
115 let startTimeStamp: number = DateTimeUtils.getDateTimestamp(this.liveDetailsBean.liveInfo.planStartTime) 127 let startTimeStamp: number = DateTimeUtils.getDateTimestamp(this.liveDetailsBean.liveInfo.planStartTime)
116 let currentTimeStamp: number = DateTimeUtils.getTimeStamp() 128 let currentTimeStamp: number = DateTimeUtils.getTimeStamp()
117 let _differenceTimeStampTmp = startTimeStamp - currentTimeStamp 129 let _differenceTimeStampTmp = startTimeStamp - currentTimeStamp
118 - this.isCountDownStart = _differenceTimeStampTmp <= 4 * 60 * 60 * 1000 130 + this.isCountDownStart = _differenceTimeStampTmp <= 0 ? false : _differenceTimeStampTmp <= 4 * 60 * 60 * 1000
119 if (this.isCountDownStart) { 131 if (this.isCountDownStart) {
120 this.differenceTimeStamp = _differenceTimeStampTmp 132 this.differenceTimeStamp = _differenceTimeStampTmp
121 return 133 return
@@ -129,6 +141,34 @@ export struct LiveCountdownComponent { @@ -129,6 +141,34 @@ export struct LiveCountdownComponent {
129 this.minute = playStartTimeTmp.substring(14, 16) 141 this.minute = playStartTimeTmp.substring(14, 16)
130 } 142 }
131 } 143 }
  144 +
  145 + getLiveAppointmentStatus() {
  146 + this.liveViewModel.getLiveAppointmentStatus(
  147 + this.liveDetailsBean.reLInfo ? this.liveDetailsBean.reLInfo.relId : '',
  148 + this.liveDetailsBean.newsId
  149 + ).then(
  150 + (data) => {
  151 + this.isAppointmentLive = data
  152 + },
  153 + () => {
  154 +
  155 + })
  156 + }
  157 +
  158 + liveAppointment() {
  159 + this.liveViewModel.liveAppointment(
  160 + this.liveDetailsBean.reLInfo ? this.liveDetailsBean.reLInfo.relId : '',
  161 + this.liveDetailsBean.newsId,
  162 + !this.isAppointmentLive).then(
  163 + (data) => {
  164 + if (data.success) {
  165 + this.isAppointmentLive = !this.isAppointmentLive
  166 + }
  167 + },
  168 + () => {
  169 +
  170 + })
  171 + }
132 } 172 }
133 173
134 @Extend(Text) 174 @Extend(Text)
1 -import { LiveRoomItemBean } from 'wdBean/Index' 1 +import { LiveDetailsBean, LiveRoomItemBean } from 'wdBean/Index'
2 import { ListHasNoMoreDataUI } from 'wdComponent/Index' 2 import { ListHasNoMoreDataUI } from 'wdComponent/Index'
  3 +import { StringUtils } from 'wdKit/Index'
3 import { LiveViewModel } from '../../viewModel/LiveViewModel' 4 import { LiveViewModel } from '../../viewModel/LiveViewModel'
4 import { TabLiveItemComponent } from './TabLiveItemComponent' 5 import { TabLiveItemComponent } from './TabLiveItemComponent'
5 6
@@ -7,6 +8,7 @@ import { TabLiveItemComponent } from './TabLiveItemComponent' @@ -7,6 +8,7 @@ import { TabLiveItemComponent } from './TabLiveItemComponent'
7 export struct TabLiveComponent { 8 export struct TabLiveComponent {
8 liveViewModel: LiveViewModel = new LiveViewModel() 9 liveViewModel: LiveViewModel = new LiveViewModel()
9 @State liveList: Array<LiveRoomItemBean> = [] 10 @State liveList: Array<LiveRoomItemBean> = []
  11 + @Consume liveDetailsBean: LiveDetailsBean
10 12
11 aboutToAppear(): void { 13 aboutToAppear(): void {
12 this.getLiveList() 14 this.getLiveList()
@@ -40,6 +42,20 @@ export struct TabLiveComponent { @@ -40,6 +42,20 @@ export struct TabLiveComponent {
40 this.liveViewModel.getLiveList(1, '20000016257', '20000016229', 20,) 42 this.liveViewModel.getLiveList(1, '20000016257', '20000016229', 20,)
41 .then( 43 .then(
42 (data) => { 44 (data) => {
  45 + /**
  46 + * 在直播聊天添加一条新内容逻辑:
  47 + 判断 oldNewsId:迁移id非空 且 直播状态不是预约:"wait"
  48 + 消息内容:
  49 + 1.头像固定:APP默认头像
  50 + 2.名称固定:人民日报主持人
  51 + 3.内容:详情接口的简介,newIntroduction
  52 + */
  53 + if (StringUtils.isNotEmpty(this.liveDetailsBean.oldNewsId) && this.liveDetailsBean && this.liveDetailsBean.liveInfo.liveState != 'wait') {
  54 + let liveRoomItemBeanTemp: LiveRoomItemBean = {} as LiveRoomItemBean
  55 + liveRoomItemBeanTemp.text = this.liveDetailsBean.newIntroduction
  56 + liveRoomItemBeanTemp.senderUserName = '人民日报主持人'
  57 + data.barrageResponses.push(liveRoomItemBeanTemp)
  58 + }
43 this.liveList = data.barrageResponses 59 this.liveList = data.barrageResponses
44 }, 60 },
45 () => { 61 () => {
1 import { LiveRoomItemBean } from 'wdBean/Index' 1 import { LiveRoomItemBean } from 'wdBean/Index'
2 -import { DateTimeUtils } from 'wdKit/Index' 2 +import { DateTimeUtils, StringUtils } from 'wdKit/Index'
3 3
4 @Component 4 @Component
5 export struct TabLiveItemComponent { 5 export struct TabLiveItemComponent {
@@ -12,7 +12,7 @@ export struct TabLiveItemComponent { @@ -12,7 +12,7 @@ export struct TabLiveItemComponent {
12 build() { 12 build() {
13 Column() { 13 Column() {
14 Row() { 14 Row() {
15 - Image(this.item.senderUserAvatarUrl) 15 + Image(StringUtils.isEmpty(this.item.senderUserAvatarUrl) ? $r('app.media.default_head') : this.item.senderUserAvatarUrl)
16 .borderRadius(90) 16 .borderRadius(90)
17 .width(24) 17 .width(24)
18 .height(24) 18 .height(24)
@@ -46,6 +46,7 @@ export struct TabLiveItemComponent { @@ -46,6 +46,7 @@ export struct TabLiveItemComponent {
46 .fontWeight(400) 46 .fontWeight(400)
47 .fontColor('#999999') 47 .fontColor('#999999')
48 .margin({ left: 8 }) 48 .margin({ left: 8 })
  49 + .visibility(StringUtils.isNotEmpty(this.item.time) ? Visibility.Visible : Visibility.None)
49 Blank() 50 Blank()
50 Text('置顶') 51 Text('置顶')
51 .maxLines(1) 52 .maxLines(1)
  1 +import { window } from '@kit.ArkUI'
  2 +import { DateTimeUtils, NumberFormatterUtils, WindowModel } from 'wdKit/Index'
  3 +
  4 +import { devicePLSensorManager } from 'wdDetailPlayApi/Index'
  5 +import { LiveDetailsBean, LiveRoomDataBean } from 'wdBean/Index'
  6 +import { WDPlayerController } from 'wdPlayer/Index'
  7 +
  8 +@Entry
  9 +@Component
  10 +export struct PlayUIComponent {
  11 + playerController: WDPlayerController = new WDPlayerController();
  12 + //菜单键是否可见
  13 + @State isMenuVisible: boolean = true
  14 + @State isFullScreen: boolean = false
  15 + @Consume liveDetailsBean: LiveDetailsBean
  16 + @Consume liveRoomDataBean: LiveRoomDataBean
  17 + @State currentTime: string = ''
  18 + @State totalTime: string = ''
  19 + @State progressVal: number = 0;
  20 + //是否处于播放状态中
  21 + @State isPlayStatus: boolean = true
  22 +
  23 + aboutToAppear(): void {
  24 + //播放进度监听
  25 + this.playerController.onTimeUpdate = (position: number, duration: number) => {
  26 + this.currentTime = DateTimeUtils.secondToTime(position);
  27 + this.totalTime = DateTimeUtils.secondToTime(duration);
  28 + this.progressVal = Math.floor(position * 100 / duration);
  29 + }
  30 + }
  31 +
  32 + build() {
  33 + Column() {
  34 + this.getTopUIComponent()
  35 + this.getMiddleUIComponent()
  36 + this.getBottomUIComponent()
  37 + }
  38 + .width('100%')
  39 + .height('100%')
  40 + .alignItems(HorizontalAlign.Start)
  41 + }
  42 +
  43 + @Builder
  44 + getTopUIComponent() {
  45 + Column() {
  46 + Row() {
  47 + Image($r('app.media.icon_arrow_left_white'))
  48 + .width(24)
  49 + .aspectRatio(1)
  50 + .visibility(Visibility.None)
  51 + .margin({
  52 + right: 10
  53 + })
  54 + Text(this.liveDetailsBean.newsTitle)
  55 + .maxLines(1)
  56 + .textOverflow({ overflow: TextOverflow.Ellipsis })
  57 + .fontSize('16fp')
  58 + .fontWeight(500)
  59 + .fontColor(Color.White)
  60 + .textAlign(TextAlign.Start)
  61 + .layoutWeight(1)
  62 + Image($r('app.media.icon_share'))
  63 + .width(24)
  64 + .aspectRatio(1)
  65 + .visibility(Visibility.None)
  66 + }
  67 + .width('100%')
  68 + .alignItems(VerticalAlign.Center)
  69 + .margin({
  70 + bottom: 5
  71 + })
  72 +
  73 + this.getLiveStatusView()
  74 + }.width('100%')
  75 + .padding({
  76 + top: 10,
  77 + bottom: 6,
  78 + left: 10,
  79 + right: 10
  80 + })
  81 + .alignItems(HorizontalAlign.Start)
  82 + .visibility(this.isMenuVisible ? Visibility.Visible : Visibility.None)
  83 + }
  84 +
  85 + @Builder
  86 + getLiveStatusView() {
  87 + //预约
  88 + //直播中
  89 + //回看
  90 + Row() {
  91 + Text('回看')
  92 + .fontSize('11fp')
  93 + .fontWeight(400)
  94 + .fontColor(Color.White)
  95 + Image($r('app.media.icon_live_player_status_end'))
  96 + .width(12)
  97 + .height(12)
  98 + Text(`${NumberFormatterUtils.formatNumberWithWan(this.liveRoomDataBean.pv)}人参与`)
  99 + .fontSize('11fp')
  100 + .fontWeight(400)
  101 + .fontColor(Color.White)
  102 + }
  103 + .backgroundColor('#4D000000')
  104 + .padding({
  105 + left: 4,
  106 + top: 1,
  107 + right: 4,
  108 + bottom: 1
  109 + })
  110 + }
  111 +
  112 + @Builder
  113 + getMiddleUIComponent() {
  114 + Stack()
  115 + .layoutWeight(1)
  116 + .width('100%')
  117 + .onClick(() => {
  118 + this.isMenuVisible = !this.isMenuVisible
  119 + })
  120 + }
  121 +
  122 + @Builder
  123 + getBottomUIComponent() {
  124 + Row() {
  125 + this.playOrPauseBtn()
  126 + Text(this.currentTime)
  127 + .fontColor(Color.White)
  128 + .fontWeight(600)
  129 + .fontSize('12fp')
  130 + .margin({
  131 + left: 16
  132 + })
  133 +
  134 + this.playProgressView()
  135 +
  136 + Text(this.totalTime)
  137 + .fontColor(Color.White)
  138 + .fontWeight(600)
  139 + .fontSize('12fp')
  140 + .margin({
  141 + right: 16
  142 + })
  143 + Image($r('app.media.icon_live_player_full_screen'))
  144 + .width(24)
  145 + .height(24)
  146 + .onClick(() => {
  147 + this.isFullScreen = !this.isFullScreen
  148 + WindowModel.shared.setPreferredOrientation(this.isFullScreen ? window.Orientation.LANDSCAPE : window.Orientation.PORTRAIT);
  149 + devicePLSensorManager.devicePLSensorOn(this.isFullScreen ? window.Orientation.LANDSCAPE : window.Orientation.PORTRAIT);
  150 + })
  151 + }
  152 + .alignItems(VerticalAlign.Center)
  153 + .linearGradient({ angle: 0, colors: [['#99000000', 0], ['#00000000', 1]] })
  154 + .width('100%')
  155 + .padding({
  156 + left: 10,
  157 + right: 10,
  158 + top: 15,
  159 + bottom: 15
  160 + })
  161 + .visibility(this.isMenuVisible ? Visibility.Visible : Visibility.None)
  162 + }
  163 +
  164 + @Builder
  165 + playOrPauseBtn() {
  166 + //暂停、播放
  167 + Image(this.isPlayStatus ? $r('app.media.icon_live_player_pause') : $r('app.media.player_play_ic'))
  168 + .width(24)
  169 + .height(24)
  170 + .onClick(() => {
  171 + if (this.isPlayStatus) {
  172 + this.isPlayStatus = false
  173 + this.playerController.pause()
  174 + } else {
  175 + this.isPlayStatus = true
  176 + this.playerController.play()
  177 + }
  178 + })
  179 + }
  180 +
  181 + @Builder
  182 + playProgressView() {
  183 + Slider({
  184 + value: this.progressVal,
  185 + step: 1,
  186 + // style: SliderStyle.OutSet
  187 + })
  188 + .blockStyle({
  189 + type: SliderBlockType.IMAGE,
  190 + image: $r('app.media.ic_player_block')
  191 + })
  192 + .blockColor(Color.White)
  193 + .trackColor('#4DFFFFFF')
  194 + .selectedColor('#FFED2800')
  195 + .height(2)
  196 + .trackThickness(1)
  197 + .layoutWeight(1)
  198 + .margin({
  199 + left: 8,
  200 + right: 8
  201 + })
  202 + .onChange((value: number, mode: SliderChangeMode) => {
  203 + this.playerController?.setSeekTime(value, mode);
  204 + })
  205 + }
  206 +}
1 -import { WDPlayerController, WDPlayerRenderView } from 'wdPlayer/Index'; 1 +import { LiveDetailsBean } from 'wdBean/Index';
  2 +import { WDPlayerController, WDPlayerRenderLiveView } from 'wdPlayer/Index';
  3 +import { PlayUIComponent } from './PlayUIComponent';
2 4
3 @Component 5 @Component
4 export struct TopPlayComponent { 6 export struct TopPlayComponent {
5 - @Prop playUrl: string=''  
6 - aspectRatioPlayer: number = 375 / 211  
7 - @State playerController: WDPlayerController = new WDPlayerController(); 7 + @Consume @Watch('updateData') liveDetailsBean: LiveDetailsBean
  8 + playerController: WDPlayerController = new WDPlayerController();
8 9
9 aboutToAppear(): void { 10 aboutToAppear(): void {
10 - setTimeout(() => {  
11 - this.playerController.switchPlayOrPause()  
12 - },2000) 11 + this.playerController.onCanplay = () => {
  12 + this.playerController.play()
  13 + }
  14 + }
  15 +
  16 + updateData() {
  17 + if (this.liveDetailsBean.liveInfo && this.liveDetailsBean.liveInfo.vlive.length > 0) {
  18 + let playUrl: string = this.liveDetailsBean.liveInfo.vlive[0].replayUri
  19 + this.playerController.firstPlay(playUrl);
  20 + }
13 } 21 }
14 22
15 build() { 23 build() {
16 Stack() { 24 Stack() {
17 - WDPlayerRenderView({ 25 + WDPlayerRenderLiveView({
18 playerController: this.playerController, 26 playerController: this.playerController,
19 onLoad: async () => { 27 onLoad: async () => {
20 - this.playerController.firstPlay(this.playUrl);  
21 } 28 }
22 }) 29 })
23 .height('100%') 30 .height('100%')
24 .width('100%') 31 .width('100%')
25 - .onClick(() => {  
26 - console.error('WDPlayerRenderView=== onClick')  
27 - this.playerController?.switchPlayOrPause();  
28 - }) 32 + PlayUIComponent({ playerController: this.playerController })
29 } 33 }
30 .height(211) 34 .height(211)
31 - .aspectRatio(this.aspectRatioPlayer)  
32 - .backgroundColor(Color.Black) 35 + .width('100%')
33 } 36 }
34 37
35 aboutToDisappear(): void { 38 aboutToDisappear(): void {
  39 + this.playerController.pause()
36 } 40 }
37 } 41 }
@@ -9,6 +9,11 @@ @@ -9,6 +9,11 @@
9 "2in1" 9 "2in1"
10 ], 10 ],
11 "deliveryWithInstall": true, 11 "deliveryWithInstall": true,
12 - "pages": "$profile:main_pages" 12 + "pages": "$profile:main_pages",
  13 + "requestPermissions": [
  14 + {
  15 + "name":"ohos.permission.ACCELEROMETER"
  16 + }
  17 + ]
13 } 18 }
14 } 19 }
@@ -2,6 +2,8 @@ export { WDPlayerController } from "./src/main/ets/controller/WDPlayerController @@ -2,6 +2,8 @@ export { WDPlayerController } from "./src/main/ets/controller/WDPlayerController
2 2
3 export { WDPlayerRenderView } from "./src/main/ets/pages/WDPlayerRenderView" 3 export { WDPlayerRenderView } from "./src/main/ets/pages/WDPlayerRenderView"
4 4
  5 +export { WDPlayerRenderLiveView } from "./src/main/ets/pages/WDPlayerRenderLiveView"
  6 +
5 export { PlayerConstants } from "./src/main/ets/constants/PlayerConstants" 7 export { PlayerConstants } from "./src/main/ets/constants/PlayerConstants"
6 8
7 export { SpeedBean } from "./src/main/ets/bean/SpeedBean" 9 export { SpeedBean } from "./src/main/ets/bean/SpeedBean"
  1 +import componentUtils from '@ohos.arkui.componentUtils';
  2 +import { WDPlayerController } from '../controller/WDPlayerController'
  3 +import { WindowModel } from 'wdKit';
  4 +import { Logger } from '../utils/Logger';
  5 +
  6 +class Size {
  7 + width: Length = "100%";
  8 + height: Length = "100%";
  9 +
  10 + constructor(width: Length, height: Length) {
  11 + this.width = width;
  12 + this.height = height;
  13 + }
  14 +}
  15 +
  16 +let insIndex: number = 0;
  17 +const TAG = 'WDPlayerRenderLiveView'
  18 +
  19 +class MGPlayRenderViewIns {
  20 + static intCount: number = 0;
  21 +
  22 + static add() {
  23 + MGPlayRenderViewIns.intCount++;
  24 + WindowModel.shared.setWindowKeepScreenOn(true);
  25 + console.log("add-- +1")
  26 + }
  27 +
  28 + static del() {
  29 + console.log("del-- -1")
  30 + MGPlayRenderViewIns.intCount--;
  31 + if (MGPlayRenderViewIns.intCount <= 0) {
  32 + WindowModel.shared.setWindowKeepScreenOn(false);
  33 + }
  34 + }
  35 +}
  36 +
  37 +/**
  38 + * 播放窗口组件
  39 + */
  40 +@Component
  41 +export struct WDPlayerRenderLiveView {
  42 + private playerController?: WDPlayerController;
  43 + private xComponentController: XComponentController = new XComponentController();
  44 + onLoad?: ((event?: object) => void);
  45 + videoWidth: number = 0
  46 + videoHeight: number = 0
  47 + @State selfSize: Size = new Size('100%', '100%');
  48 + private insId: string = "WDPlayRenderView" + insIndex;
  49 +
  50 + aboutToAppear() {
  51 + MGPlayRenderViewIns.add();
  52 +
  53 + console.log('playerController', this.playerController)
  54 + insIndex++;
  55 + if (!this.playerController) {
  56 + return
  57 + }
  58 +
  59 + this.playerController.onVideoSizeChange = (width: number, height: number) => {
  60 + // console.log(`WDPlayerRenderView onVideoSizeChange width:${width} videoTop:${height}`)
  61 + Logger.info(TAG, ` onVideoSizeChange width:${width} videoTop:${height}`)
  62 + this.videoWidth = width;
  63 + this.videoHeight = height;
  64 + this.updateLayout()
  65 + }
  66 + }
  67 +
  68 + aboutToDisappear() {
  69 + Logger.info(TAG, `aboutToDisappear`)
  70 + MGPlayRenderViewIns.del();
  71 + }
  72 +
  73 + build() {
  74 + Row() {
  75 + // 设置为“surface“类型时XComponent组件可以和其他组件一起进行布局和渲染。
  76 + XComponent({
  77 + id: 'xComponentId',
  78 + type: 'surface',
  79 + controller: this.xComponentController
  80 + })
  81 + .onLoad(async (event) => {
  82 + Logger.info(TAG, 'onLoad')
  83 + let surfaceId = this.xComponentController.getXComponentSurfaceId()
  84 + console.log('surfaceId===', surfaceId)
  85 + console.log('insId===', this.insId)
  86 + this.xComponentController.setXComponentSurfaceSize({
  87 + surfaceWidth: 1920,
  88 + surfaceHeight: 1080
  89 + });
  90 + this.playerController?.setXComponentController(this.xComponentController)
  91 + if (this.onLoad) {
  92 + this.onLoad(event)
  93 + }
  94 + })
  95 + .width(this.selfSize.width)
  96 + .height(this.selfSize.height)
  97 + }
  98 + .id(this.insId)
  99 + .onAreaChange(() => {
  100 + // this.updateLayout()
  101 + })
  102 + .backgroundColor("#000000")
  103 + .justifyContent(FlexAlign.Center)
  104 + .height('100%')
  105 + .width('100%')
  106 + }
  107 +
  108 + updateLayout() {
  109 + let info = componentUtils.getRectangleById(this.insId);
  110 + if (info.size.width > 0 && info.size.height > 0 && this.videoHeight > 0 && this.videoWidth > 0) {
  111 + if (info.size.width / info.size.height > this.videoWidth / this.videoHeight) {
  112 + let scale = info.size.height / this.videoHeight;
  113 + this.selfSize = new Size((this.videoWidth * scale / info.size.width) * 100 + "%", '100%');
  114 + } else {
  115 + let scale = info.size.width / this.videoWidth;
  116 + this.selfSize = new Size('100%', (this.videoHeight * scale / info.size.height) * 100 + "%");
  117 + }
  118 + }
  119 + }
  120 +}