wangliang_wd

Merge branch 'main' of http://192.168.1.42/developOne/harmonyPool into main

* 'main' of http://192.168.1.42/developOne/harmonyPool:
  ref |> 处理直播上墙、回复消息、封禁、解封、预显控制、弹幕开关、结束直播、显示垫片控制
  ref |> 直播IM消息显示,处理竖屏直播
  ref |> 直播IM动态接收消息和显示。目前是横屏直播已处理
  ref |> 处理播控中心不生效问题
  ref |> 音频播放接入系统播控中心
Showing 25 changed files with 792 additions and 138 deletions
@@ -19,11 +19,11 @@ export interface LiveRoomItemBean { @@ -19,11 +19,11 @@ export interface LiveRoomItemBean {
19 //guest :嘉宾,host:主持人 19 //guest :嘉宾,host:主持人
20 role: string 20 role: string
21 //ZH_TEXT_AND_IMAGE_MSG :图文,ZH_TEXT_MSG:文本,ZH_VIDEO_MSG:视频,ZH_AUDIO_MSG:音频 21 //ZH_TEXT_AND_IMAGE_MSG :图文,ZH_TEXT_MSG:文本,ZH_VIDEO_MSG:视频,ZH_AUDIO_MSG:音频
22 - dataType: string 22 + dataType: LiveMessageOptType
23 //管理直播间的消息类型 ZH_BARRAGE_SWITCH_MSG:弹幕开关 ZH_TOP_MSG:置顶,ZH_UN_TOP_MSG:取消置顶 ZH_STOP_LIVE: 直播结束,ZH_CHANGE_PAD直播垫片等 23 //管理直播间的消息类型 ZH_BARRAGE_SWITCH_MSG:弹幕开关 ZH_TOP_MSG:置顶,ZH_UN_TOP_MSG:取消置顶 ZH_STOP_LIVE: 直播结束,ZH_CHANGE_PAD直播垫片等
24 - optionType: string 24 + optionType: LiveMessageOptType
25 ///房间类型,标识这个消息属于大家聊还是直播间,ZH_VIDEO:直播间 ZH_CHAT:大家聊 25 ///房间类型,标识这个消息属于大家聊还是直播间,ZH_VIDEO:直播间 ZH_CHAT:大家聊
26 - messageRoom: string 26 + messageRoom: LiveMessageRoomType
27 //视频封面图 27 //视频封面图
28 transcodeImageUrl: string 28 transcodeImageUrl: string
29 //视频地址 29 //视频地址
@@ -39,4 +39,72 @@ export interface LiveRoomItemBean { @@ -39,4 +39,72 @@ export interface LiveRoomItemBean {
39 //观看人次 39 //观看人次
40 pv: string 40 pv: string
41 41
  42 + ///------- from IM
  43 + // 自义定表情
  44 + customizeExpression: number
  45 + // 已登录的用户id
  46 + senderUserId?: string
  47 + // 未登录的设备id
  48 + deviceId?: string
  49 + // data字段,评论预显开关
  50 + data?: string
  51 + // receiver
  52 + receiverText?: string
  53 + receiverTime?: string
  54 + receiverUserId?: string
  55 + receiverUserName?: string
  56 + receiverAvatarUrl?: string
  57 +
  58 + // 自定义字段
  59 + customFormIM?: boolean // 默认来自网络接口
  60 +}
  61 +
  62 +export enum LiveMessageOptType {
  63 + ZH_BARRAGE_SWITCH_MSG = "ZH_BARRAGE_SWITCH_MSG",
  64 + ZH_UPDATE_MSG = "ZH_UPDATE_MSG",
  65 + ZH_DELETE_MSG = "ZH_DELETE_MSG",
  66 + ZH_TOP_MSG = "ZH_TOP_MSG",
  67 + ZH_UN_TOP_MSG = "ZH_UN_TOP_MSG",
  68 + ZH_STOP_LIVE = "ZH_STOP_LIVE",
  69 + ZH_CHANGE_PAD = "ZH_CHANGE_PAD",
  70 + ZH_PRE_DISPLAY_CHANGE = "ZH_PRE_DISPLAY_CHANGE",
  71 + ZH_TEXT_MSG = "ZH_TEXT_MSG",
  72 + ZH_IMAGE_MSG = "ZH_IMAGE_MSG",
  73 + ZH_TEXT_AND_IMAGE_MSG = "ZH_TEXT_AND_IMAGE_MSG",
  74 + ZH_WALL_MSG = "ZH_WALL_MSG",
  75 + ZH_AUDIO_MSG = "ZH_AUDIO_MSG",
  76 + ZH_VIDEO_MSG = "ZH_VIDEO_MSG",
  77 + ZH_REPLY_MSG = "ZH_REPLY_MSG",
  78 + ZH_ROOM_NUMBER_MSG = "ZH_ROOM_NUMBER_MSG",
  79 + ZH_BARRAGE_BAN_MESSAGE = "ZH_BARRAGE_BAN_MESSAGE",
  80 + ZH_BARRAGE_UNBAN_MESSAGE = "ZH_BARRAGE_UNBAN_MESSAGE",
  81 + ZH_VOTE_MESSAGE = "ZH_VOTE_MESSAGE",
  82 + ZH_START_LIVE = "ZH_START_LIVE",
  83 +}
  84 +
  85 +export enum LiveMessageRoomType {
  86 + living = "ZH_VIDEO", // 直播间
  87 + chat = "ZH_CHAT", // 大家聊
  88 +}
  89 +
  90 +export enum LiveMessageRole {
  91 + host = "host",
  92 + guest = "guest",
  93 + tourist = "tourist",
  94 +}
  95 +
  96 +export function LiveMessageIsHistoryMessage(optionType: LiveMessageOptType): boolean {
  97 + let isHistoryMessage = false
  98 + switch (optionType) {
  99 + case LiveMessageOptType.ZH_TEXT_MSG:
  100 + case LiveMessageOptType.ZH_IMAGE_MSG:
  101 + case LiveMessageOptType.ZH_TEXT_AND_IMAGE_MSG:
  102 + case LiveMessageOptType.ZH_AUDIO_MSG:
  103 + case LiveMessageOptType.ZH_VIDEO_MSG: {
  104 + isHistoryMessage = true
  105 + } break;
  106 + default:
  107 + break;
  108 + }
  109 + return isHistoryMessage
42 } 110 }
1 import { DateTimeUtils, Logger } from 'wdKit/Index'; 1 import { DateTimeUtils, Logger } from 'wdKit/Index';
2 -import { WDPlayerController } from 'wdPlayer/Index'; 2 +import { PlayerConstants, WDPlayerController } from 'wdPlayer/Index';
3 3
4 let TAG: string = 'AudioRowComponent' 4 let TAG: string = 'AudioRowComponent'
5 5
@@ -12,6 +12,16 @@ export struct AudioRowComponent { @@ -12,6 +12,16 @@ export struct AudioRowComponent {
12 @State isPlaying: boolean = false 12 @State isPlaying: boolean = false
13 13
14 aboutToAppear(): void { 14 aboutToAppear(): void {
  15 + this.playerController.onCanplay = () => {
  16 + this.playerController.play()
  17 + }
  18 + this.playerController.onStatusChange = (status: number) => {
  19 + if (status == PlayerConstants.STATUS_START) {
  20 + this.isPlaying = true
  21 + } else {
  22 + this.isPlaying = false
  23 + }
  24 + }
15 this.playerController.firstPlay(this.audioUrl) 25 this.playerController.firstPlay(this.audioUrl)
16 // this.playerController.onTimeUpdate = (nowSeconds, totalSeconds) => { 26 // this.playerController.onTimeUpdate = (nowSeconds, totalSeconds) => {
17 // console.log('现在时间', nowSeconds) 27 // console.log('现在时间', nowSeconds)
@@ -30,7 +40,7 @@ export struct AudioRowComponent { @@ -30,7 +40,7 @@ export struct AudioRowComponent {
30 left: 8, 40 left: 8,
31 right: 6 41 right: 6
32 }) 42 })
33 - .visibility(this.isPlaying ? Visibility.Visible : Visibility.Hidden) 43 + .visibility(this.isPlaying ? Visibility.Hidden : Visibility.Visible)
34 Text(`${DateTimeUtils.getFormattedDuration(this.duration)}`) 44 Text(`${DateTimeUtils.getFormattedDuration(this.duration)}`)
35 .fontColor('#666666') 45 .fontColor('#666666')
36 .fontWeight(400) 46 .fontWeight(400)
@@ -73,7 +73,7 @@ export struct LiveOperRowListView { @@ -73,7 +73,7 @@ export struct LiveOperRowListView {
73 private isLlive = false 73 private isLlive = false
74 /// comment 74 /// comment
75 @State showCommentInput: boolean = false 75 @State showCommentInput: boolean = false
76 - private banComment: boolean = true // 是否已禁言 76 + @Consume banComment: boolean // 上层可以直播IM控制
77 private commentInputDialogController?: CustomDialogController 77 private commentInputDialogController?: CustomDialogController
78 @State publishCommentModel: publishCommentModel = new publishCommentModel() 78 @State publishCommentModel: publishCommentModel = new publishCommentModel()
79 79
@@ -372,7 +372,7 @@ export struct OperRowListView { @@ -372,7 +372,7 @@ export struct OperRowListView {
372 .gesture( 372 .gesture(
373 TapGesture() 373 TapGesture()
374 .onAction((event: GestureEvent) => { 374 .onAction((event: GestureEvent) => {
375 - this.AudioSuspension.setPlayerUrl(this.audioUrl, this.audioTitle) 375 + this.AudioSuspension.setPlayerUrl(this.audioUrl, this.audioTitle, this.contentDetailData.newsId + "", this.contentDetailData.newsSourceName)
376 TrackingButton.click('suspendedWindow',this.pageId,this.pageName) 376 TrackingButton.click('suspendedWindow',this.pageId,this.pageName)
377 })) 377 }))
378 } 378 }
1 import window from '@ohos.window'; 1 import window from '@ohos.window';
2 import { Logger } from 'wdKit'; 2 import { Logger } from 'wdKit';
3 -import { WDPlayerController } from 'wdPlayer'; 3 +import { BackgroundAudioController, WDPlayerController } from 'wdPlayer';
4 import { BusinessError } from '@ohos.base'; 4 import { BusinessError } from '@ohos.base';
5 import { EmitterEventId, EmitterUtils } from 'wdKit/Index' 5 import { EmitterEventId, EmitterUtils } from 'wdKit/Index'
6 6
@@ -39,9 +39,18 @@ export class AudioSuspensionModel { @@ -39,9 +39,18 @@ export class AudioSuspensionModel {
39 /** 39 /**
40 * 配置音频地址 40 * 配置音频地址
41 */ 41 */
42 - public setPlayerUrl(url: string, srcTitle: string) { 42 + public async setPlayerUrl(url: string, srcTitle: string, srcContentId?: string, srcSource?: string) {
43 /*console.log(TAG,'this.url', this.url) 43 /*console.log(TAG,'this.url', this.url)
44 console.log(TAG,'url', url)*/ 44 console.log(TAG,'url', url)*/
  45 + this.playerController.get().keepOnBackground = true
  46 + BackgroundAudioController.sharedController().avplayerController = this.playerController.get()
  47 + await BackgroundAudioController.sharedController().createSession()
  48 + BackgroundAudioController.sharedController().startContinuousTask()
  49 + let id = $r('app.media.newspaper_default').id
  50 + BackgroundAudioController.sharedController().setSessionMetaData(srcContentId ?? "", srcTitle, 'file://', srcSource ?? "")
  51 + BackgroundAudioController.sharedController().stopUseFeatures()
  52 + BackgroundAudioController.sharedController().listenPlayEvents()
  53 +
45 if (this.url === url) { 54 if (this.url === url) {
46 this.isMinimize = AppStorage.link<boolean>('isMinimize') 55 this.isMinimize = AppStorage.link<boolean>('isMinimize')
47 console.log(TAG, 'this.isMinimize', this.isMinimize?.get()) 56 console.log(TAG, 'this.isMinimize', this.isMinimize?.get())
@@ -10,6 +10,8 @@ import { LiveRoomItemBean } from 'wdBean/Index'; @@ -10,6 +10,8 @@ import { LiveRoomItemBean } from 'wdBean/Index';
10 import { LiveRoomBaseInfo } from './LiveRoomBaseInfo' 10 import { LiveRoomBaseInfo } from './LiveRoomBaseInfo'
11 11
12 import { JSON } from '@kit.ArkTS'; 12 import { JSON } from '@kit.ArkTS';
  13 +import { LiveMessageIsHistoryMessage, LiveMessageOptType } from 'wdBean/src/main/ets/bean/live/LiveRoomBean';
  14 +
13 const TAG = "LiveRoomManager" 15 const TAG = "LiveRoomManager"
14 16
15 export class LiveRoom extends ChatroomStatusListener { 17 export class LiveRoom extends ChatroomStatusListener {
@@ -32,7 +34,7 @@ export class LiveRoom extends ChatroomStatusListener { @@ -32,7 +34,7 @@ export class LiveRoom extends ChatroomStatusListener {
32 34
33 enterRoom() { 35 enterRoom() {
34 let roomId = this.connectRoomBaseInfo?.roomID 36 let roomId = this.connectRoomBaseInfo?.roomID
35 - let msgCount = 10; 37 + let msgCount = 0;
36 38
37 Logger.debug(TAG, `will enterRoom roomId: ${roomId}`); 39 Logger.debug(TAG, `will enterRoom roomId: ${roomId}`);
38 IMEngine.getInstance().joinExistingChatroom(roomId, msgCount).then(result => { 40 IMEngine.getInstance().joinExistingChatroom(roomId, msgCount).then(result => {
@@ -107,7 +109,8 @@ export class LiveRoom extends ChatroomStatusListener { @@ -107,7 +109,8 @@ export class LiveRoom extends ChatroomStatusListener {
107 109
108 let optionType = liveRoomItemBean.optionType != undefined ? liveRoomItemBean.optionType : liveRoomItemBean.dataType 110 let optionType = liveRoomItemBean.optionType != undefined ? liveRoomItemBean.optionType : liveRoomItemBean.dataType
109 111
110 - if (this.isHistoryMessage(optionType)) { 112 + if (LiveMessageIsHistoryMessage(optionType)) {
  113 + liveRoomItemBean.customFormIM = true
111 if (this.onHistoryMessage) { 114 if (this.onHistoryMessage) {
112 this.onHistoryMessage(liveRoomItemBean) 115 this.onHistoryMessage(liveRoomItemBean)
113 } 116 }
@@ -120,20 +123,4 @@ export class LiveRoom extends ChatroomStatusListener { @@ -120,20 +123,4 @@ export class LiveRoom extends ChatroomStatusListener {
120 123
121 124
122 } 125 }
123 -  
124 - isHistoryMessage(optionType: string): boolean {  
125 - let isHistoryMessage = false  
126 - switch (optionType) {  
127 - case "ZH_TEXT_MSG":  
128 - case "ZH_IMAGE_MSG":  
129 - case "ZH_TEXT_AND_IMAGE_MSG":  
130 - case "ZH_AUDIO_MSG":  
131 - case "ZH_VIDEO_MSG": {  
132 - isHistoryMessage = true  
133 - } break;  
134 - default:  
135 - break;  
136 - }  
137 - return isHistoryMessage  
138 - }  
139 } 126 }
@@ -116,6 +116,7 @@ export class LiveRoomManager { @@ -116,6 +116,7 @@ export class LiveRoomManager {
116 } 116 }
117 117
118 private connectWithIMToken(token: string) { 118 private connectWithIMToken(token: string) {
  119 + Logger.debug(TAG, "连接开始,IM Token:" + token)
119 const timeout = 5 120 const timeout = 5
120 IMEngine.getInstance().connect(token, timeout).then(result => { 121 IMEngine.getInstance().connect(token, timeout).then(result => {
121 if (EngineError.Success === result.code) { 122 if (EngineError.Success === result.code) {
@@ -12,6 +12,8 @@ import { publishCommentModel } from 'wdComponent/src/main/ets/components/comment @@ -12,6 +12,8 @@ import { publishCommentModel } from 'wdComponent/src/main/ets/components/comment
12 import { TrackConstants, TrackingContent, TrackParamConvert } from 'wdTracking/Index'; 12 import { TrackConstants, TrackingContent, TrackParamConvert } from 'wdTracking/Index';
13 import { onlyWifiLoadVideo } from 'wdComponent/src/main/ets/utils/lazyloadImg'; 13 import { onlyWifiLoadVideo } from 'wdComponent/src/main/ets/utils/lazyloadImg';
14 import { LiveDetailChatRoomController } from '../im/LiveDetailChatRoomController'; 14 import { LiveDetailChatRoomController } from '../im/LiveDetailChatRoomController';
  15 +import { LiveMessageOptType, LiveMessageRoomType } from 'wdBean/src/main/ets/bean/live/LiveRoomBean';
  16 +import { LiveDetailPageLogic } from '../viewModel/LiveDetailPageLogic';
15 17
16 let TAG: string = 'DetailPlayLivePage'; 18 let TAG: string = 'DetailPlayLivePage';
17 19
@@ -40,9 +42,13 @@ export struct DetailPlayLivePage { @@ -40,9 +42,13 @@ export struct DetailPlayLivePage {
40 // 尽量不要动属性。用来作为输入了评论之后,值传递 42 // 尽量不要动属性。用来作为输入了评论之后,值传递
41 @State lastInputedLiveComment: LiveRoomItemBean = {} as LiveRoomItemBean // 上次输入的直播间消息 43 @State lastInputedLiveComment: LiveRoomItemBean = {} as LiveRoomItemBean // 上次输入的直播间消息
42 @State lastInputedChatComment: LiveRoomItemBean = {} as LiveRoomItemBean // 上次输入的大家聊消息 44 @State lastInputedChatComment: LiveRoomItemBean = {} as LiveRoomItemBean // 上次输入的大家聊消息
  45 + @State lastLiveControl: LiveRoomItemBean = {} as LiveRoomItemBean // IM 控制消息
43 // 顶部状态栏高度 46 // 顶部状态栏高度
44 @Consume topSafeHeight: number 47 @Consume topSafeHeight: number
45 chatRoomController: LiveDetailChatRoomController = new LiveDetailChatRoomController() 48 chatRoomController: LiveDetailChatRoomController = new LiveDetailChatRoomController()
  49 + @Provide banComment: boolean = true
  50 + @State isEnd: boolean = false
  51 + @Consume liveDetailPageLogic: LiveDetailPageLogic
46 52
47 @State toastText: ResourceStr = "这是一个非Wi-Fi环境。请注意流量消耗" 53 @State toastText: ResourceStr = "这是一个非Wi-Fi环境。请注意流量消耗"
48 dialogToast: CustomDialogController = new CustomDialogController({ 54 dialogToast: CustomDialogController = new CustomDialogController({
@@ -83,30 +89,79 @@ export struct DetailPlayLivePage { @@ -83,30 +89,79 @@ export struct DetailPlayLivePage {
83 if(!await onlyWifiLoadVideo()){ 89 if(!await onlyWifiLoadVideo()){
84 this.showToastTip(this.toastText) 90 this.showToastTip(this.toastText)
85 } 91 }
  92 + this.configChatRoom()
  93 + }
  94 +
  95 + async aboutToDisappear() {
  96 + Logger.info(TAG, `wyj-aboutToDisappear`)
  97 + await this.playerController?.stop()
  98 + await this.playerController?.release()
  99 + }
  100 +
  101 + configChatRoom() {
86 this.chatRoomController.onHistoryMessage = (liveRoomItemBean: LiveRoomItemBean) => { 102 this.chatRoomController.onHistoryMessage = (liveRoomItemBean: LiveRoomItemBean) => {
87 - if (liveRoomItemBean.messageRoom == "ZH_VIDEO") { 103 + const preDisplay = this.contentDetailData.liveInfo.preCommentFlag == 1
  104 + if (this.liveViewModel.filterMySelfCommentNoPreDisplay(liveRoomItemBean, preDisplay)) {
  105 + return
  106 + }
  107 + if (liveRoomItemBean.messageRoom == LiveMessageRoomType.living) {
88 this.lastInputedLiveComment = liveRoomItemBean 108 this.lastInputedLiveComment = liveRoomItemBean
89 - } else if (liveRoomItemBean.messageRoom == "ZH_CHAT") { 109 + } else if (liveRoomItemBean.messageRoom == LiveMessageRoomType.chat) {
90 this.lastInputedChatComment = liveRoomItemBean 110 this.lastInputedChatComment = liveRoomItemBean
91 } 111 }
92 } 112 }
93 this.chatRoomController.onLiveMessage = (liveRoomItemBean: LiveRoomItemBean) => { 113 this.chatRoomController.onLiveMessage = (liveRoomItemBean: LiveRoomItemBean) => {
94 - if (liveRoomItemBean.optionType == "ZH_ROOM_NUMBER_MSG") { 114 + switch (liveRoomItemBean.optionType) {
  115 + case LiveMessageOptType.ZH_ROOM_NUMBER_MSG: {
95 this.liveRoomDataBean.pv = Number(liveRoomItemBean.pv) 116 this.liveRoomDataBean.pv = Number(liveRoomItemBean.pv)
  117 + } break
  118 + case LiveMessageOptType.ZH_BARRAGE_SWITCH_MSG: {
  119 + const openComment = liveRoomItemBean.data == "1"
  120 + this.contentDetailData.liveInfo.openComment = openComment ? 1 : 0
  121 + } break
  122 + case LiveMessageOptType.ZH_PRE_DISPLAY_CHANGE: {
  123 + const preCommentFlag = liveRoomItemBean.data == "1"
  124 + this.contentDetailData.liveInfo.preCommentFlag = preCommentFlag ? 1 : 0
  125 + } break
  126 + case LiveMessageOptType.ZH_BARRAGE_BAN_MESSAGE:
  127 + case LiveMessageOptType.ZH_BARRAGE_UNBAN_MESSAGE: {
  128 + const banComment = liveRoomItemBean.data == "1"
  129 + this.banComment = banComment
  130 + } break
  131 + case LiveMessageOptType.ZH_STOP_LIVE:
  132 + case LiveMessageOptType.ZH_START_LIVE:
  133 + case LiveMessageOptType.ZH_CHANGE_PAD: { // 直播垫片控制
  134 + this.lastLiveControl = liveRoomItemBean
  135 + } break
  136 + case LiveMessageOptType.ZH_REPLY_MSG: {
  137 + this.lastInputedChatComment = liveRoomItemBean
  138 + } break
  139 + case LiveMessageOptType.ZH_UPDATE_MSG: {
  140 + this.lastInputedLiveComment = liveRoomItemBean
  141 + } break
  142 + case LiveMessageOptType.ZH_DELETE_MSG: {
  143 + this.lastInputedLiveComment = liveRoomItemBean
  144 + } break
  145 + case LiveMessageOptType.ZH_TOP_MSG: {
  146 + this.lastInputedLiveComment = liveRoomItemBean
  147 + } break
  148 + case LiveMessageOptType.ZH_UN_TOP_MSG: {
  149 + this.lastInputedLiveComment = liveRoomItemBean
  150 + } break
  151 + case LiveMessageOptType.ZH_WALL_MSG: {
  152 + this.lastInputedLiveComment = liveRoomItemBean
  153 + } break
  154 + default: {
  155 + Logger.warn(TAG, "暂未处理类型:" + liveRoomItemBean.optionType)
  156 + } break
96 } 157 }
97 } 158 }
98 this.chatRoomController.configDetail(this.contentDetailData) 159 this.chatRoomController.configDetail(this.contentDetailData)
99 } 160 }
100 161
101 - async aboutToDisappear() {  
102 - Logger.info(TAG, `wyj-aboutToDisappear`)  
103 - await this.playerController?.stop()  
104 - await this.playerController?.release()  
105 - }  
106 -  
107 build() { 162 build() {
108 Column() { 163 Column() {
109 - TopPlayComponent({ playerController: this.playerController }) 164 + TopPlayComponent({ playerController: this.playerController, isEnd: this.isEnd, lastLiveControl: this.lastLiveControl })
110 .height(this.displayDirection == DisplayDirection.VERTICAL ? 211 : '100%') 165 .height(this.displayDirection == DisplayDirection.VERTICAL ? 211 : '100%')
111 .margin({ 166 .margin({
112 top: this.displayDirection == DisplayDirection.VERTICAL ? px2vp(this.topSafeHeight) : 0 167 top: this.displayDirection == DisplayDirection.VERTICAL ? px2vp(this.topSafeHeight) : 0
1 -import { ContentDetailDTO, LiveRoomDataBean } from 'wdBean/Index'; 1 +import { ContentDetailDTO, LiveRoomDataBean, LiveRoomItemBean } from 'wdBean/Index';
2 import { LiveViewModel } from '../viewModel/LiveViewModel'; 2 import { LiveViewModel } from '../viewModel/LiveViewModel';
3 -import { CustomToast, WindowModel } from 'wdKit/Index'; 3 +import { CustomToast, Logger, WindowModel } from 'wdKit/Index';
4 import { PlayerComponent } from '../widgets/vertical/PlayerComponent'; 4 import { PlayerComponent } from '../widgets/vertical/PlayerComponent';
5 import { PlayerInfoComponent } from '../widgets/vertical/PlayerInfoComponent'; 5 import { PlayerInfoComponent } from '../widgets/vertical/PlayerInfoComponent';
6 import { WDAliPlayerController } from 'wdPlayer/Index'; 6 import { WDAliPlayerController } from 'wdPlayer/Index';
@@ -12,6 +12,8 @@ import { LiveDetailPageLogic } from '../viewModel/LiveDetailPageLogic'; @@ -12,6 +12,8 @@ import { LiveDetailPageLogic } from '../viewModel/LiveDetailPageLogic';
12 import { onlyWifiLoadVideo } from 'wdComponent/src/main/ets/utils/lazyloadImg'; 12 import { onlyWifiLoadVideo } from 'wdComponent/src/main/ets/utils/lazyloadImg';
13 import { StringUtils } from 'wdKit'; 13 import { StringUtils } from 'wdKit';
14 import { LiveDetailChatRoomController } from '../im/LiveDetailChatRoomController'; 14 import { LiveDetailChatRoomController } from '../im/LiveDetailChatRoomController';
  15 +import { LiveMessageOptType } from 'wdBean/src/main/ets/bean/live/LiveRoomBean';
  16 +import { JSON } from '@kit.ArkTS';
15 17
16 const storage = LocalStorage.getShared(); 18 const storage = LocalStorage.getShared();
17 const TAG = 'DetailPlayVLivePage' 19 const TAG = 'DetailPlayVLivePage'
@@ -40,11 +42,13 @@ export struct DetailPlayVLivePage { @@ -40,11 +42,13 @@ export struct DetailPlayVLivePage {
40 @Consume contentId: string 42 @Consume contentId: string
41 @State swiperIndex: number = 1 43 @State swiperIndex: number = 1
42 @Consume liveDetailPageLogic: LiveDetailPageLogic 44 @Consume liveDetailPageLogic: LiveDetailPageLogic
  45 + @Provide lastInputedComment: LiveRoomItemBean = {} as LiveRoomItemBean // 上次输入的消息
43 //播放错误 46 //播放错误
44 @State isPlayerError: boolean = false 47 @State isPlayerError: boolean = false
45 @State isCanplay: boolean = false 48 @State isCanplay: boolean = false
46 @State toastText: ResourceStr = "这是一个非Wi-Fi环境。请注意流量消耗" 49 @State toastText: ResourceStr = "这是一个非Wi-Fi环境。请注意流量消耗"
47 chatRoomController: LiveDetailChatRoomController = new LiveDetailChatRoomController() 50 chatRoomController: LiveDetailChatRoomController = new LiveDetailChatRoomController()
  51 + @Provide banComment: boolean = true
48 52
49 dialogToast: CustomDialogController = new CustomDialogController({ 53 dialogToast: CustomDialogController = new CustomDialogController({
50 builder: CustomToast({ 54 builder: CustomToast({
@@ -71,7 +75,7 @@ export struct DetailPlayVLivePage { @@ -71,7 +75,7 @@ export struct DetailPlayVLivePage {
71 if(!await onlyWifiLoadVideo()){ 75 if(!await onlyWifiLoadVideo()){
72 this.showToastTip(this.toastText) 76 this.showToastTip(this.toastText)
73 } 77 }
74 - this.chatRoomController.configDetail(this.contentDetailData) 78 + this.configChatRoom()
75 } 79 }
76 80
77 aboutToDisappear(): void { 81 aboutToDisappear(): void {
@@ -92,6 +96,49 @@ export struct DetailPlayVLivePage { @@ -92,6 +96,49 @@ export struct DetailPlayVLivePage {
92 // WindowModel.shared.setWindowSystemBarProperties({ statusBarContentColor: '#000000', }) 96 // WindowModel.shared.setWindowSystemBarProperties({ statusBarContentColor: '#000000', })
93 } 97 }
94 98
  99 + configChatRoom() {
  100 + this.chatRoomController.onHistoryMessage = (liveRoomItemBean: LiveRoomItemBean) => {
  101 + const preDisplay = this.contentDetailData.liveInfo.preCommentFlag == 1
  102 + if (this.liveViewModel.filterMySelfCommentNoPreDisplay(liveRoomItemBean, preDisplay)) {
  103 + return
  104 + }
  105 + this.lastInputedComment = liveRoomItemBean
  106 + }
  107 + this.chatRoomController.onLiveMessage = (liveRoomItemBean: LiveRoomItemBean) => {
  108 + switch (liveRoomItemBean.optionType) {
  109 + case LiveMessageOptType.ZH_ROOM_NUMBER_MSG: {
  110 + this.liveRoomDataBean.pv = Number(liveRoomItemBean.pv)
  111 + } break
  112 + case LiveMessageOptType.ZH_BARRAGE_SWITCH_MSG: {
  113 + const openComment = liveRoomItemBean.data == "1"
  114 + this.contentDetailData.liveInfo.openComment = openComment ? 1 : 0
  115 + } break
  116 + case LiveMessageOptType.ZH_PRE_DISPLAY_CHANGE: {
  117 + const preCommentFlag = liveRoomItemBean.data == "1"
  118 + this.contentDetailData.liveInfo.preCommentFlag = preCommentFlag ? 1 : 0
  119 + } break
  120 + case LiveMessageOptType.ZH_BARRAGE_BAN_MESSAGE:
  121 + case LiveMessageOptType.ZH_BARRAGE_UNBAN_MESSAGE: {
  122 + const banComment = liveRoomItemBean.data == "1"
  123 + this.banComment = banComment
  124 + } break
  125 + case LiveMessageOptType.ZH_STOP_LIVE: {
  126 + this.liveState = "end"
  127 + this.contentDetailData.liveInfo.liveState = "end"
  128 + } break
  129 + case LiveMessageOptType.ZH_CHANGE_PAD: {
  130 + const padObj = JSON.parse(liveRoomItemBean.data ?? "") as Record<string, string | number | boolean>
  131 + const showPad = padObj["showPad"] == "1"
  132 + this.liveDetailPageLogic.showPad = showPad
  133 + } break
  134 + default: {
  135 + Logger.warn(TAG, "暂未处理类型:" + liveRoomItemBean.optionType)
  136 + } break
  137 + }
  138 + }
  139 + this.chatRoomController.configDetail(this.contentDetailData)
  140 + }
  141 +
95 build() { 142 build() {
96 143
97 Stack({ alignContent: Alignment.Top }) { 144 Stack({ alignContent: Alignment.Top }) {
@@ -117,7 +164,7 @@ export struct DetailPlayVLivePage { @@ -117,7 +164,7 @@ export struct DetailPlayVLivePage {
117 // 有垫片 164 // 有垫片
118 if (this.liveDetailPageLogic.padImageUri.length > 0) { 165 if (this.liveDetailPageLogic.padImageUri.length > 0) {
119 // 配置了垫片资源 166 // 配置了垫片资源
120 - Image(this.liveDetailPageLogic.padImageUri).objectFit(ImageFit.Fill).width('100%').height('100%') 167 + Image(this.liveDetailPageLogic.padImageUri).objectFit(ImageFit.Contain).width('100%').height('100%')
121 168
122 } else { 169 } else {
123 // 没有配置垫片资源 170 // 没有配置垫片资源
1 -import { HttpUrlUtils, HttpUtils, ResponseDTO } from 'wdNetwork'; 1 +import { HttpBizUtil, HttpUrlUtils, HttpUtils, ResponseDTO } from 'wdNetwork';
2 import { HttpRequest } from 'wdNetwork/src/main/ets/http/HttpRequest'; 2 import { HttpRequest } from 'wdNetwork/src/main/ets/http/HttpRequest';
3 import { Logger, ToastUtils, EmitterEventId, EmitterUtils, SPHelper } from 'wdKit'; 3 import { Logger, ToastUtils, EmitterEventId, EmitterUtils, SPHelper } from 'wdKit';
4 import { ContentDetailDTO, LiveDetailsBean, LiveRoomBean, LiveRoomDataBean, 4 import { ContentDetailDTO, LiveDetailsBean, LiveRoomBean, LiveRoomDataBean,
@@ -6,6 +6,7 @@ import { ContentDetailDTO, LiveDetailsBean, LiveRoomBean, LiveRoomDataBean, @@ -6,6 +6,7 @@ import { ContentDetailDTO, LiveDetailsBean, LiveRoomBean, LiveRoomDataBean,
6 ReserveItemBean, ValueType } from 'wdBean/Index'; 6 ReserveItemBean, ValueType } from 'wdBean/Index';
7 import { ContentDetailRequest } from 'wdDetailPlayApi/Index'; 7 import { ContentDetailRequest } from 'wdDetailPlayApi/Index';
8 import { SpConstants } from 'wdConstant/Index'; 8 import { SpConstants } from 'wdConstant/Index';
  9 +import { LiveMessageOptType } from 'wdBean/src/main/ets/bean/live/LiveRoomBean';
9 10
10 const TAG = 'LiveModel' 11 const TAG = 'LiveModel'
11 12
@@ -113,7 +114,7 @@ export class LiveModel { @@ -113,7 +114,7 @@ export class LiveModel {
113 params['liveId'] = liveId 114 params['liveId'] = liveId
114 params['pageSize'] = pageSize + '' 115 params['pageSize'] = pageSize + ''
115 return new Promise<LiveRoomBean>((success, fail) => { 116 return new Promise<LiveRoomBean>((success, fail) => {
116 - HttpRequest.post<ResponseDTO<LiveRoomBean>>( 117 + HttpBizUtil.post<ResponseDTO<LiveRoomBean>>(
117 HttpUrlUtils.getLiveChatListUrl(), 118 HttpUrlUtils.getLiveChatListUrl(),
118 params, 119 params,
119 ).then((data: ResponseDTO<LiveRoomBean>) => { 120 ).then((data: ResponseDTO<LiveRoomBean>) => {
@@ -321,7 +322,7 @@ export class LiveModel { @@ -321,7 +322,7 @@ export class LiveModel {
321 let commentItem: LiveRoomItemBean = {} as LiveRoomItemBean 322 let commentItem: LiveRoomItemBean = {} as LiveRoomItemBean
322 commentItem.text = comment 323 commentItem.text = comment
323 commentItem.isWall = 0 324 commentItem.isWall = 0
324 - commentItem.dataType = "ZH_TEXT_MSG" 325 + commentItem.dataType = LiveMessageOptType.ZH_TEXT_MSG
325 326
326 let params: Record<string, string | number> = {}; 327 let params: Record<string, string | number> = {};
327 params["liveId"] = liveId 328 params["liveId"] = liveId
@@ -7,10 +7,12 @@ import { @@ -7,10 +7,12 @@ import {
7 LiveRoomItemBean, 7 LiveRoomItemBean,
8 ValueType 8 ValueType
9 } from 'wdBean/Index' 9 } from 'wdBean/Index'
  10 +import { LiveMessageIsHistoryMessage } from 'wdBean/src/main/ets/bean/live/LiveRoomBean'
  11 +import { SpConstants } from 'wdConstant'
10 import { ContentDetailRequest } from 'wdDetailPlayApi/Index' 12 import { ContentDetailRequest } from 'wdDetailPlayApi/Index'
11 -import { Logger } from 'wdKit/Index' 13 +import { Logger, SPHelper } from 'wdKit/Index'
12 import { ToastUtils } from 'wdKit/src/main/ets/utils/ToastUtils' 14 import { ToastUtils } from 'wdKit/src/main/ets/utils/ToastUtils'
13 -import { ResponseDTO } from 'wdNetwork/Index' 15 +import { HttpUtils, ResponseDTO } from 'wdNetwork/Index'
14 import { LiveModel } from './LiveModel' 16 import { LiveModel } from './LiveModel'
15 17
16 const TAG = "LiveViewModel" 18 const TAG = "LiveViewModel"
@@ -196,4 +198,37 @@ export class LiveViewModel { @@ -196,4 +198,37 @@ export class LiveViewModel {
196 retItem.fullColumnImgUrlDto = item.fullColumnImgUrlDto 198 retItem.fullColumnImgUrlDto = item.fullColumnImgUrlDto
197 return retItem 199 return retItem
198 } 200 }
  201 +
  202 + filterMySelfCommentNoPreDisplay(comment: LiveRoomItemBean, openPreDisplay:boolean) {
  203 + let mySelf = false
  204 + const userId = HttpUtils.getUserId()
  205 + const deviceId = HttpUtils.getDeviceId()
  206 + if (comment.senderUserId && comment.senderUserId == userId) {
  207 + mySelf = true
  208 + } else if (comment.deviceId && comment.deviceId == deviceId) {
  209 + mySelf = true
  210 + }
  211 + let optionType = comment.optionType != undefined ? comment.optionType : comment.dataType
  212 +
  213 + if (mySelf
  214 + && openPreDisplay
  215 + && comment.customFormIM === true
  216 + && LiveMessageIsHistoryMessage(optionType)
  217 + ) {
  218 + return true
  219 + }
  220 + return false
  221 + }
  222 +
  223 + isMySelfComment(comment: LiveRoomItemBean) {
  224 + let mySelf = false
  225 + const userId = HttpUtils.getUserId()
  226 + const deviceId = HttpUtils.getDeviceId()
  227 + if (comment.senderUserId && comment.senderUserId == userId) {
  228 + mySelf = true
  229 + } else if (comment.deviceId && comment.deviceId == deviceId) {
  230 + mySelf = true
  231 + }
  232 + return mySelf
  233 + }
199 } 234 }
@@ -53,7 +53,9 @@ export struct TabChatComponent { @@ -53,7 +53,9 @@ export struct TabChatComponent {
53 53
54 this.liveChatList.push(info) 54 this.liveChatList.push(info)
55 this.pageModel.viewType = ViewType.LOADED; 55 this.pageModel.viewType = ViewType.LOADED;
56 - // this.scroller.scrollEdge(Edge.Bottom) 56 + if (this.pageModel.viewType == ViewType.LOADED) {
  57 + this.scroller.scrollEdge(Edge.Bottom)
  58 + }
57 console.log(TAG, '发布评论:', JSON.stringify(this.publishCommentModel.lastCommentModel)) 59 console.log(TAG, '发布评论:', JSON.stringify(this.publishCommentModel.lastCommentModel))
58 } 60 }
59 } 61 }
@@ -63,6 +65,9 @@ export struct TabChatComponent { @@ -63,6 +65,9 @@ export struct TabChatComponent {
63 lastInputedCommentChanged(info: string) { 65 lastInputedCommentChanged(info: string) {
64 Logger.debug(TAG, "2显示评论》》》: " + JSON.stringify(this.lastInputedComment)) 66 Logger.debug(TAG, "2显示评论》》》: " + JSON.stringify(this.lastInputedComment))
65 this.liveChatList.push(this.liveViewModel.deepCopyLiveRoomItem(this.lastInputedComment)) 67 this.liveChatList.push(this.liveViewModel.deepCopyLiveRoomItem(this.lastInputedComment))
  68 + if (this.pageModel.viewType == ViewType.LOADED) {
  69 + this.scroller.scrollEdge(Edge.Bottom)
  70 + }
66 this.pageModel.viewType = ViewType.LOADED; 71 this.pageModel.viewType = ViewType.LOADED;
67 } 72 }
68 73
@@ -3,6 +3,7 @@ import { Logger, StringUtils } from 'wdKit/Index' @@ -3,6 +3,7 @@ import { Logger, StringUtils } from 'wdKit/Index'
3 // import { Action, LiveRoomItemBean, Params, PhotoListBean } from 'wdBean/Index' 3 // import { Action, LiveRoomItemBean, Params, PhotoListBean } from 'wdBean/Index'
4 import { WDRouterRule } from 'wdRouter' 4 import { WDRouterRule } from 'wdRouter'
5 import { ExtraDTO } from 'wdBean/src/main/ets/bean/component/extra/ExtraDTO' 5 import { ExtraDTO } from 'wdBean/src/main/ets/bean/component/extra/ExtraDTO'
  6 +import { LiveMessageOptType, LiveMessageRole } from 'wdBean/src/main/ets/bean/live/LiveRoomBean'
6 7
7 const TAG = "TabChatItemComponent" 8 const TAG = "TabChatItemComponent"
8 9
@@ -22,62 +23,20 @@ export struct TabChatItemComponent { @@ -22,62 +23,20 @@ export struct TabChatItemComponent {
22 .width(24) 23 .width(24)
23 .height(24) 24 .height(24)
24 Column() { 25 Column() {
25 - if (this.item.dataType == 'ZH_IMAGE_MSG') {  
26 - Row() {  
27 - Text() {  
28 - Span(this.item.senderUserName + ': ')  
29 - .fontColor('#666666')  
30 - }  
31 - .margin({ left: 8 })  
32 - .lineHeight(20)  
33 - .layoutWeight(1)  
34 - .fontSize('14fp')  
35 - .fontWeight(400)  
36 - }  
37 - .alignItems(VerticalAlign.Top) 26 + this.messageContentText()
38 27
39 if (this.item.pictureUrls && this.item.pictureUrls.length > 0) { 28 if (this.item.pictureUrls && this.item.pictureUrls.length > 0) {
40 Image(this.item.pictureUrls[0]) 29 Image(this.item.pictureUrls[0])
41 - .width(`100%`) 30 + .width(this.item.customizeExpression === 1 ? 72 : `100%`)
42 .objectFit(ImageFit.Contain) 31 .objectFit(ImageFit.Contain)
43 .borderRadius(4) 32 .borderRadius(4)
44 .margin({ 33 .margin({
45 - top: 10 34 + top: 10, bottom: 4
46 }) 35 })
47 .onClick(() => { 36 .onClick(() => {
48 this.gotoMultipleListImagePage(this.item.pictureUrls[0]) 37 this.gotoMultipleListImagePage(this.item.pictureUrls[0])
49 }) 38 })
50 } 39 }
51 - } else {  
52 - Row() {  
53 - Text() {  
54 - Span(this.item.senderUserName + ': ')  
55 - .fontColor('#666666')  
56 - Span(this.item.text)  
57 - .fontColor('#222222')  
58 - }  
59 - .margin({ left: 8 })  
60 - .lineHeight(20)  
61 - .layoutWeight(1)  
62 - .fontSize('14fp')  
63 - .fontWeight(400)  
64 - }  
65 - .alignItems(VerticalAlign.Top)  
66 -  
67 - if (this.item.dataType == 'ZH_TEXT_AND_IMAGE_MSG' && this.item.pictureUrls &&  
68 - this.item.pictureUrls.length > 0) {  
69 - Image(this.item.pictureUrls[0])  
70 - .width(`100%`)  
71 - .objectFit(ImageFit.Contain)  
72 - .borderRadius(4)  
73 - .margin({  
74 - top: 10  
75 - })  
76 - .onClick(() => {  
77 - this.gotoMultipleListImagePage(this.item.pictureUrls[0])  
78 - })  
79 - }  
80 - }  
81 40
82 } 41 }
83 .margin({ 42 .margin({
@@ -96,6 +55,47 @@ export struct TabChatItemComponent { @@ -96,6 +55,47 @@ export struct TabChatItemComponent {
96 55
97 } 56 }
98 57
  58 + @Builder messageContentText() {
  59 + Row() {
  60 + Text() {
  61 +
  62 + if (this.item.receiverUserName && this.item.receiverUserName.length > 0) {
  63 + /// 回复的消息处理
  64 +
  65 + Span((this.item.senderUserName ?? "游客") + " ").fontColor('#2696FF')
  66 + if (this.item.role == LiveMessageRole.host) {
  67 + Span(' 主持人 ')
  68 + .fontSize(11)
  69 + .lineHeight(20)
  70 + .textBackgroundStyle({ color: "#70FFC63F", radius: 2 })
  71 + Span(' ')
  72 + }
  73 + if (this.item.role == LiveMessageRole.guest) {
  74 + Span(' 嘉宾 ')
  75 + .fontSize(11)
  76 + .lineHeight(20)
  77 + .textBackgroundStyle({ color: "#70FFC63F", radius: 2 })
  78 + Span(' ')
  79 + }
  80 + Span("回复了 ").fontColor('#222222')
  81 + Span((this.item.receiverUserName ?? "游客") + ': ').fontColor('#666666')
  82 + Span(this.item.text).fontColor('#222222')
  83 +
  84 + } else {
  85 + // 普通消息
  86 + Span((this.item.senderUserName ?? "游客") + ': ').fontColor('#666666')
  87 + Span(this.item.text).fontColor('#222222')
  88 + }
  89 + }
  90 + .margin({ left: 8 })
  91 + .lineHeight(20)
  92 + .layoutWeight(1)
  93 + .fontSize('14fp')
  94 + .fontWeight(400)
  95 + }
  96 + .alignItems(VerticalAlign.Top)
  97 + }
  98 +
99 /** 99 /**
100 * 大图列表页 100 * 大图列表页
101 * @param content 101 * @param content
@@ -10,6 +10,7 @@ import { LiveViewModel } from '../../viewModel/LiveViewModel' @@ -10,6 +10,7 @@ import { LiveViewModel } from '../../viewModel/LiveViewModel'
10 import { Logger } from 'wdKit' 10 import { Logger } from 'wdKit'
11 import LoadMoreLayout from 'wdComponent/src/main/ets/components/page/LoadMoreLayout' 11 import LoadMoreLayout from 'wdComponent/src/main/ets/components/page/LoadMoreLayout'
12 import { PeopleShipNoMoreData } from 'wdComponent/src/main/ets/components/reusable/PeopleShipNoMoreData' 12 import { PeopleShipNoMoreData } from 'wdComponent/src/main/ets/components/reusable/PeopleShipNoMoreData'
  13 +import { LiveMessageOptType } from 'wdBean/src/main/ets/bean/live/LiveRoomBean'
13 14
14 const TAG: string = 'TabLiveComponent'; 15 const TAG: string = 'TabLiveComponent';
15 16
@@ -34,7 +35,29 @@ export struct TabLiveComponent { @@ -34,7 +35,29 @@ export struct TabLiveComponent {
34 35
35 lastInputedCommentChanged(info: string) { 36 lastInputedCommentChanged(info: string) {
36 Logger.debug(TAG, "1显示评论》》》: " + JSON.stringify(this.lastInputedComment)) 37 Logger.debug(TAG, "1显示评论》》》: " + JSON.stringify(this.lastInputedComment))
  38 + switch (this.lastInputedComment.optionType) {
  39 + case LiveMessageOptType.ZH_UPDATE_MSG: {
  40 +
  41 + } break
  42 + case LiveMessageOptType.ZH_DELETE_MSG: {
  43 +
  44 + } break
  45 + case LiveMessageOptType.ZH_TOP_MSG: {
  46 +
  47 + } break
  48 + case LiveMessageOptType.ZH_UN_TOP_MSG: {
  49 +
  50 + } break
  51 + case LiveMessageOptType.ZH_WALL_MSG: {
  52 +
  53 + } break
  54 + }
  55 +
  56 + if (this.liveList.totalCount() === 0) {
37 this.liveList.push(this.liveViewModel.deepCopyLiveRoomItem(this.lastInputedComment)) 57 this.liveList.push(this.liveViewModel.deepCopyLiveRoomItem(this.lastInputedComment))
  58 + } else {
  59 + this.liveList.addFirstItem(this.liveViewModel.deepCopyLiveRoomItem(this.lastInputedComment))
  60 + }
38 this.pageModel.viewType = ViewType.LOADED; 61 this.pageModel.viewType = ViewType.LOADED;
39 } 62 }
40 63
@@ -190,7 +213,7 @@ export struct TabLiveComponent { @@ -190,7 +213,7 @@ export struct TabLiveComponent {
190 liveRoomItemBeanTemp.senderUserName = '人民日报主持人' 213 liveRoomItemBeanTemp.senderUserName = '人民日报主持人'
191 liveRoomItemBeanTemp.pictureUrls = [] 214 liveRoomItemBeanTemp.pictureUrls = []
192 liveRoomItemBeanTemp.pictureUrls.push(this.contentDetailData?.fullColumnImgUrls[0]?.url) 215 liveRoomItemBeanTemp.pictureUrls.push(this.contentDetailData?.fullColumnImgUrls[0]?.url)
193 - liveRoomItemBeanTemp.dataType = 'ZH_TEXT_AND_IMAGE_MSG' 216 + liveRoomItemBeanTemp.dataType = LiveMessageOptType.ZH_TEXT_AND_IMAGE_MSG
194 let temp = this.contentDetailData?.fullColumnImgUrls[0] 217 let temp = this.contentDetailData?.fullColumnImgUrls[0]
195 if (temp) { 218 if (temp) {
196 liveRoomItemBeanTemp.pictureResolutions = [] 219 liveRoomItemBeanTemp.pictureResolutions = []
1 import { Action, LiveRoomItemBean, Params, PhotoListBean } from 'wdBean/Index' 1 import { Action, LiveRoomItemBean, Params, PhotoListBean } from 'wdBean/Index'
2 import { ExtraDTO } from 'wdBean/src/main/ets/bean/component/extra/ExtraDTO' 2 import { ExtraDTO } from 'wdBean/src/main/ets/bean/component/extra/ExtraDTO'
  3 +import { LiveMessageOptType, LiveMessageRole } from 'wdBean/src/main/ets/bean/live/LiveRoomBean'
3 import { AudioRowComponent } from 'wdComponent/Index' 4 import { AudioRowComponent } from 'wdComponent/Index'
4 import { DateTimeUtils, StringUtils } from 'wdKit/Index' 5 import { DateTimeUtils, StringUtils } from 'wdKit/Index'
5 import { WDRouterRule } from 'wdRouter/Index' 6 import { WDRouterRule } from 'wdRouter/Index'
@@ -83,7 +84,7 @@ export struct TabLiveItemComponent { @@ -83,7 +84,7 @@ export struct TabLiveItemComponent {
83 .textAlign(TextAlign.Start) 84 .textAlign(TextAlign.Start)
84 //ZH_TEXT_AND_IMAGE_MSG :图文,ZH_TEXT_MSG:文本,ZH_VIDEO_MSG:视频,ZH_AUDIO_MSG:音频 85 //ZH_TEXT_AND_IMAGE_MSG :图文,ZH_TEXT_MSG:文本,ZH_VIDEO_MSG:视频,ZH_AUDIO_MSG:音频
85 //图文 86 //图文
86 - if (this.item.dataType === 'ZH_TEXT_AND_IMAGE_MSG') { 87 + if (this.item.dataType === LiveMessageOptType.ZH_TEXT_AND_IMAGE_MSG) {
87 List({ space: this.item.pictureUrls.length == 1 ? 0 : 5 }) { 88 List({ space: this.item.pictureUrls.length == 1 ? 0 : 5 }) {
88 ForEach(this.item.pictureUrls, (itemSub: string, index: number) => { 89 ForEach(this.item.pictureUrls, (itemSub: string, index: number) => {
89 ListItem() { 90 ListItem() {
@@ -112,14 +113,14 @@ export struct TabLiveItemComponent { @@ -112,14 +113,14 @@ export struct TabLiveItemComponent {
112 }) 113 })
113 } 114 }
114 //音频 115 //音频
115 - else if (this.item.dataType === 'ZH_AUDIO_MSG') { 116 + else if (this.item.dataType === LiveMessageOptType.ZH_AUDIO_MSG) {
116 AudioRowComponent({ 117 AudioRowComponent({
117 audioUrl: this.item.audioUrl, 118 audioUrl: this.item.audioUrl,
118 duration: this.item.duration 119 duration: this.item.duration
119 }) 120 })
120 } 121 }
121 //视频 122 //视频
122 - else if (this.item.dataType === 'ZH_VIDEO_MSG') { 123 + else if (this.item.dataType === LiveMessageOptType.ZH_VIDEO_MSG) {
123 RelativeContainer() { 124 RelativeContainer() {
124 Image(this.item.transcodeImageUrl) 125 Image(this.item.transcodeImageUrl)
125 .width('100%') 126 .width('100%')
@@ -154,6 +155,11 @@ export struct TabLiveItemComponent { @@ -154,6 +155,11 @@ export struct TabLiveItemComponent {
154 this.gotoVideoPlayPage() 155 this.gotoVideoPlayPage()
155 }) 156 })
156 } 157 }
  158 +
  159 + // 上墙或回复消息
  160 + else if (this.item.receiverUserName && this.item.receiverUserName.length > 0) {
  161 + this.wallOrReplySubMessage()
  162 + }
157 } 163 }
158 .margin({ 164 .margin({
159 left: 8, 165 left: 8,
@@ -233,4 +239,64 @@ export struct TabLiveItemComponent { @@ -233,4 +239,64 @@ export struct TabLiveItemComponent {
233 } 239 }
234 return 1 240 return 1
235 } 241 }
  242 +
  243 + // 上墙子消息
  244 + @Builder wallOrReplySubMessage() {
  245 + Row() {
  246 + Image(StringUtils.isEmpty(this.item.receiverAvatarUrl) ? $r('app.media.default_head') : this.item.receiverAvatarUrl)
  247 + .borderRadius(90)
  248 + .width(24)
  249 + .height(24)
  250 + Column() {
  251 + Row() {
  252 + Text() {
  253 + Span((this.item.receiverUserName ?? "游客") + ': ').fontColor('#666666')
  254 + }.lineHeight(20).fontSize('14fp').fontWeight(400)
  255 +
  256 + if (this.item.isWall == 1) {
  257 + Blank().layoutWeight(1)
  258 +
  259 + Text() {
  260 + Span(' 上墙 ')
  261 + .foregroundColor("#CB0000")
  262 + .fontSize(11)
  263 + .lineHeight(20)
  264 + .textBackgroundStyle({ color: "#70FFC63F", radius: 2 })
  265 + }.lineHeight(20).fontSize('14fp').fontWeight(400)
  266 + }
  267 + }
  268 + .alignItems(VerticalAlign.Top)
  269 +
  270 + Text() {
  271 + Span(this.item.receiverText ?? "").fontColor('#222222')
  272 + }
  273 + .margin({top: 8})
  274 + .lineHeight(20)
  275 + .fontSize('14fp')
  276 + .fontWeight(400)
  277 +
  278 + if (this.item.pictureUrls && this.item.pictureUrls.length > 0) {
  279 + Image(this.item.pictureUrls[0])
  280 + .width(this.item.customizeExpression === 1 ? 72 : `100%`)
  281 + .objectFit(ImageFit.Contain)
  282 + .borderRadius(4)
  283 + .margin({
  284 + top: 10, bottom: 4
  285 + })
  286 + .onClick(() => {
  287 + this.gotoMultipleListImagePage(0)
  288 + })
  289 + }
  290 +
  291 + }
  292 + .margin({
  293 + left: 8,
  294 + right: 8
  295 + })
  296 + .layoutWeight(1)
  297 + .alignItems(HorizontalAlign.Start)
  298 + }
  299 + .alignItems(VerticalAlign.Top)
  300 + .padding({ top: 15 })
  301 + }
236 } 302 }
1 -import { ContentDetailDTO } from 'wdBean/Index'; 1 +import { ContentDetailDTO, LiveRoomItemBean } from 'wdBean/Index';
2 import { Logger, StringUtils } from 'wdKit/Index'; 2 import { Logger, StringUtils } from 'wdKit/Index';
3 import { PlayerConstants, WDAliPlayerController, WDPlayerRenderLiveView } from 'wdPlayer/Index'; 3 import { PlayerConstants, WDAliPlayerController, WDPlayerRenderLiveView } from 'wdPlayer/Index';
4 import { PlayUIComponent } from './PlayUIComponent'; 4 import { PlayUIComponent } from './PlayUIComponent';
@@ -6,6 +6,7 @@ import { PictureLoading } from '../../vertical/PictureLoading'; @@ -6,6 +6,7 @@ import { PictureLoading } from '../../vertical/PictureLoading';
6 import { TrackConstants } from 'wdTracking/Index'; 6 import { TrackConstants } from 'wdTracking/Index';
7 import { LiveDetailPageLogic } from '../../../viewModel/LiveDetailPageLogic'; 7 import { LiveDetailPageLogic } from '../../../viewModel/LiveDetailPageLogic';
8 import { LiveEmptyComponent, WDLiveViewDefaultType } from 'wdComponent/Index'; 8 import { LiveEmptyComponent, WDLiveViewDefaultType } from 'wdComponent/Index';
  9 +import { LiveMessageOptType } from 'wdBean/src/main/ets/bean/live/LiveRoomBean';
9 10
10 const TAG: string = 'TopPlayComponent' 11 const TAG: string = 'TopPlayComponent'
11 12
@@ -23,7 +24,7 @@ export struct TopPlayComponent { @@ -23,7 +24,7 @@ export struct TopPlayComponent {
23 //未开始 24 //未开始
24 @State isWait: boolean = false 25 @State isWait: boolean = false
25 //已结束直播 26 //已结束直播
26 - @State isEnd: boolean = false 27 + @Link isEnd: boolean
27 //播放错误 28 //播放错误
28 @State isPlayerError: boolean = false 29 @State isPlayerError: boolean = false
29 // loading 控制字段 30 // loading 控制字段
@@ -38,6 +39,7 @@ export struct TopPlayComponent { @@ -38,6 +39,7 @@ export struct TopPlayComponent {
38 @Consume @Watch('pageShowChange') pageShow: number 39 @Consume @Watch('pageShowChange') pageShow: number
39 @Consume @Watch('pageHideChange') pageHide: number 40 @Consume @Watch('pageHideChange') pageHide: number
40 init: boolean = false 41 init: boolean = false
  42 + @Prop @Watch("liveIMControlMessageChange") lastLiveControl: LiveRoomItemBean = {} as LiveRoomItemBean // IM 控制消息
41 43
42 pageShowChange() { 44 pageShowChange() {
43 this.playerController?.play() 45 this.playerController?.play()
@@ -166,6 +168,27 @@ export struct TopPlayComponent { @@ -166,6 +168,27 @@ export struct TopPlayComponent {
166 // `---0------>` + this.isWait + ' ->' + this.isHideLoading + ' ->' + this.isEnd + ' -->' + this.isVideoSource) 168 // `---0------>` + this.isWait + ' ->' + this.isHideLoading + ' ->' + this.isEnd + ' -->' + this.isVideoSource)
167 } 169 }
168 170
  171 + liveIMControlMessageChange() {
  172 + switch (this.lastLiveControl.optionType) {
  173 + case LiveMessageOptType.ZH_STOP_LIVE: {
  174 + this.liveDetailPageLogic.showPad = false
  175 + this.contentDetailData.liveInfo.liveState = "end"
  176 + this.isEnd = true
  177 + }
  178 + break
  179 + case LiveMessageOptType.ZH_START_LIVE: {
  180 + // TODO:
  181 + }
  182 + break
  183 + case LiveMessageOptType.ZH_CHANGE_PAD: { // 直播垫片控制
  184 + const padObj = JSON.parse(this.lastLiveControl.data ?? "") as Record<string, string | number | boolean>
  185 + const showPad = padObj["showPad"] == "1"
  186 + this.liveDetailPageLogic.showPad = showPad
  187 + }
  188 + break
  189 + }
  190 + }
  191 +
169 tryToPlay() { 192 tryToPlay() {
170 193
171 if (!this.xComponentIsLoaded) { 194 if (!this.xComponentIsLoaded) {
1 import { LiveRoomItemBean } from 'wdBean/Index' 1 import { LiveRoomItemBean } from 'wdBean/Index'
  2 +import { LiveMessageRole } from 'wdBean/src/main/ets/bean/live/LiveRoomBean'
  3 +import { LengthMetrics } from '@kit.ArkUI'
2 4
3 @Component 5 @Component
4 export struct ChatItemComponent { 6 export struct ChatItemComponent {
@@ -10,17 +12,23 @@ export struct ChatItemComponent { @@ -10,17 +12,23 @@ export struct ChatItemComponent {
10 build() { 12 build() {
11 Row() { 13 Row() {
12 Text() { 14 Text() {
13 - // if (this.item.senderUserName) {  
14 - // Span(' 主持人 ')  
15 - // .fontSize(11)  
16 - // .lineHeight(20)  
17 - // .textBackgroundStyle({ color: '#808562', radius: 2 })  
18 - // Span(' ')  
19 - // } 15 + if (this.item.role == LiveMessageRole.host) {
  16 + Span(' 主持人 ')
  17 + .fontSize(11)
  18 + .lineHeight(20)
  19 + .textBackgroundStyle({ color: "#70FFC63F", radius: 2 })
  20 + Span(' ')
  21 + }
  22 + if (this.item.role == LiveMessageRole.guest) {
  23 + Span(' 嘉宾 ')
  24 + .fontSize(11)
  25 + .lineHeight(20)
  26 + .textBackgroundStyle({ color: "#70FFC63F", radius: 2 })
  27 + Span(' ')
  28 + }
20 Span(this.item.senderUserName + ': ') 29 Span(this.item.senderUserName + ': ')
21 .fontColor('#FFFFC63F') 30 .fontColor('#FFFFC63F')
22 .padding({ right: 118 }) 31 .padding({ right: 118 })
23 - //  
24 32
25 Span(this.item.text) 33 Span(this.item.text)
26 } 34 }
1 import { Action, ContentDetailDTO, LiveDetailsBean, LiveRoomDataBean, LiveRoomItemBean } from 'wdBean/Index' 1 import { Action, ContentDetailDTO, LiveDetailsBean, LiveRoomDataBean, LiveRoomItemBean } from 'wdBean/Index'
2 -import { LiveCommentComponent } from 'wdComponent/Index'  
3 -import { publishCommentModel } from 'wdComponent/src/main/ets/components/comment/model/PublishCommentModel'  
4 import { LiveOperRowListView } from 'wdComponent' 2 import { LiveOperRowListView } from 'wdComponent'
5 import PageModel from 'wdComponent/src/main/ets/viewmodel/PageModel' 3 import PageModel from 'wdComponent/src/main/ets/viewmodel/PageModel'
6 import { DisplayDirection, SpConstants, ViewType } from 'wdConstant/Index' 4 import { DisplayDirection, SpConstants, ViewType } from 'wdConstant/Index'
@@ -25,8 +23,8 @@ export struct PlayerCommentComponent { @@ -25,8 +23,8 @@ export struct PlayerCommentComponent {
25 @Consume displayDirection: DisplayDirection 23 @Consume displayDirection: DisplayDirection
26 @State private pageModel: PageModel = new PageModel() 24 @State private pageModel: PageModel = new PageModel()
27 @State liveChatList: Array<LiveRoomItemBean> = [] 25 @State liveChatList: Array<LiveRoomItemBean> = []
  26 + @Consume @Watch("lastInputedCommentChagned") lastInputedComment: LiveRoomItemBean
28 @Consume @Watch('liveDetailsBeanChange') contentDetailData: ContentDetailDTO 27 @Consume @Watch('liveDetailsBeanChange') contentDetailData: ContentDetailDTO
29 - @Consume publishCommentModel: publishCommentModel  
30 scroller: Scroller = new Scroller() 28 scroller: Scroller = new Scroller()
31 29
32 async aboutToAppear(): Promise<void> { 30 async aboutToAppear(): Promise<void> {
@@ -81,6 +79,16 @@ export struct PlayerCommentComponent { @@ -81,6 +79,16 @@ export struct PlayerCommentComponent {
81 }) 79 })
82 } 80 }
83 81
  82 + lastInputedCommentChagned() {
  83 + Logger.debug(TAG, "2显示评论》》》: " + JSON.stringify(this.lastInputedComment))
  84 +
  85 + this.liveChatList.push(this.lastInputedComment)
  86 + if (this.pageModel.viewType == ViewType.LOADED) {
  87 + this.scroller.scrollEdge(Edge.Bottom)
  88 + }
  89 + this.pageModel.viewType = ViewType.LOADED;
  90 + }
  91 +
84 build() { 92 build() {
85 Column() { 93 Column() {
86 Stack({ alignContent: Alignment.BottomStart }) { 94 Stack({ alignContent: Alignment.BottomStart }) {
@@ -17,3 +17,5 @@ export { WDAliPlayerController } from "./src/main/ets/controller/WDAliPlayerCont @@ -17,3 +17,5 @@ export { WDAliPlayerController } from "./src/main/ets/controller/WDAliPlayerCont
17 export { WDListPlayerData, WDAliListPlayerController } from "./src/main/ets/controller/WDAliListPlayerController" 17 export { WDListPlayerData, WDAliListPlayerController } from "./src/main/ets/controller/WDAliListPlayerController"
18 18
19 export { AliPlayerRenderView } from "./src/main/ets/pages/AliPlayerRenderView" 19 export { AliPlayerRenderView } from "./src/main/ets/pages/AliPlayerRenderView"
  20 +
  21 +export { BackgroundAudioController } from "./src/main/ets/controller/BackgroundAudioController"
  1 +import { Context, WantAgent, wantAgent } from '@kit.AbilityKit'
  2 +import { avSession as AVSessionManager } from '@kit.AVSessionKit'
  3 +import { backgroundTaskManager } from '@kit.BackgroundTasksKit'
  4 +import { BusinessError } from '@kit.BasicServicesKit'
  5 +import { Logger } from 'wdKit/Index'
  6 +import { PlayerConstants } from '../constants/PlayerConstants'
  7 +import { WDPlayerController } from './WDPlayerController'
  8 +import { image } from '@kit.ImageKit'
  9 +
  10 +const TAG = "BackgroundAudioController"
  11 +
  12 +export class BackgroundAudioController {
  13 +
  14 + private static bgAudioController: BackgroundAudioController
  15 + private constructor() {
  16 + }
  17 + public static sharedController() {
  18 + if (!BackgroundAudioController.bgAudioController) {
  19 + BackgroundAudioController.bgAudioController = new BackgroundAudioController()
  20 + }
  21 + return BackgroundAudioController.bgAudioController
  22 + }
  23 +
  24 + public gotContextFunc?: () => Context
  25 + public avplayerController?: WDPlayerController
  26 + private lastSession?: AVSessionManager.AVSession
  27 + private applyedLongTaskPlay: boolean = false
  28 + private lastProgress: number = 0.0
  29 + private hasSetupProgress: boolean = false
  30 +
  31 + // 开始创建并激活媒体会话
  32 + // 创建session
  33 + async createSession() {
  34 + if (!this.gotContextFunc) { return }
  35 +
  36 + if (this.lastSession == null) {
  37 + this.destorySession()
  38 + let type: AVSessionManager.AVSessionType = 'audio';
  39 + let session = await AVSessionManager.createAVSession(this.gotContextFunc(),'SESSION_NAME', type);
  40 + this.lastSession = session
  41 + }
  42 +
  43 + // 激活接口要在元数据、控制命令注册完成之后再执行
  44 + await this.lastSession?.activate();
  45 + Logger.debug(TAG, `session create done : sessionId : ${this.lastSession?.sessionId}`);
  46 +
  47 + this.lastProgress = 0
  48 + this.hasSetupProgress = false
  49 + }
  50 +
  51 + destorySession() {
  52 + if (this.lastSession) {
  53 + this.lastSession.deactivate();
  54 + this.lastSession.destroy();
  55 + }
  56 + }
  57 +
  58 + //设置播放元数据
  59 + setSessionMetaData(assetId: string, title: string, mediaImage: image.PixelMap | string, artist: string) {
  60 + Logger.debug(TAG, `SetAVMetadata assetId: ${assetId}}, title: ${title}, mediaImage: ${mediaImage}, artist: ${artist}`);
  61 + let metadata: AVSessionManager.AVMetadata = {
  62 + assetId: assetId.length > 0 ? assetId : "fake-asset-id",
  63 + title: title.length > 0 ? title : " ",
  64 + mediaImage: mediaImage,
  65 + artist: artist.length > 0 ? artist : "人日日报",
  66 + };
  67 + this.lastSession?.setAVMetadata(metadata).then(() => {
  68 + Logger.debug(TAG, `SetAVMetadata successfully`);
  69 + }).catch((err: BusinessError) => {
  70 + Logger.error(TAG, `Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
  71 + });
  72 + }
  73 +
  74 + //设置播放状态
  75 + setSessionPlayStatus(playStatus: number) {
  76 + let playbackStatus = AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY
  77 + switch (playStatus){
  78 + case PlayerConstants.STATUS_PAUSE: {
  79 + playbackStatus = AVSessionManager.PlaybackState.PLAYBACK_STATE_PAUSE
  80 + } break
  81 + case PlayerConstants.STATUS_START: {
  82 + playbackStatus = AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY
  83 + } break
  84 + case PlayerConstants.STATUS_STOP: {
  85 + playbackStatus = AVSessionManager.PlaybackState.PLAYBACK_STATE_STOP
  86 + } break
  87 + case PlayerConstants.STATUS_ERROR: {
  88 + playbackStatus = AVSessionManager.PlaybackState.PLAYBACK_STATE_ERROR
  89 + } break
  90 + case PlayerConstants.STATUS_COMPLETION: {
  91 + playbackStatus = AVSessionManager.PlaybackState.PLAYBACK_STATE_COMPLETED
  92 + } break
  93 + default: {
  94 + playbackStatus = AVSessionManager.PlaybackState.PLAYBACK_STATE_IDLE
  95 + } break
  96 + }
  97 + let playbackState: AVSessionManager.AVPlaybackState = {
  98 + state:playbackStatus,
  99 + // isFavorite:false
  100 + };
  101 + this.lastSession?.setAVPlaybackState(playbackState, (err: BusinessError) => {
  102 + if (err) {
  103 + Logger.error(TAG, `Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
  104 + } else {
  105 + Logger.debug(TAG, `SetAVPlaybackState 设置播放状态成功 ` + playStatus);
  106 + }
  107 + });
  108 + }
  109 +
  110 + //设置进度,单位秒
  111 + setSessionPlayProgress(progressDuration: number, totalDuration: number) {
  112 + // Logger.debug(TAG, `set progress: ` + progressDuration + " duration: " + totalDuration);
  113 + if (totalDuration <= 0) {
  114 + return
  115 + }
  116 + let newProgress = progressDuration / totalDuration
  117 + if (Math.abs(newProgress - this.lastProgress) < 0.01) {
  118 + return
  119 + }
  120 + this.lastProgress = newProgress
  121 +
  122 + if (this.hasSetupProgress) {
  123 + return
  124 + }
  125 + this.hasSetupProgress = true
  126 +
  127 + Logger.debug(TAG, `set progress: ` + progressDuration + " duration: " + totalDuration);
  128 +
  129 + // 设置状态: 播放状态,进度位置,播放倍速,缓存的时间
  130 + let playbackState: AVSessionManager.AVPlaybackState = {
  131 + state: AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY, // 播放状态
  132 + position: {
  133 + elapsedTime: progressDuration * 1000, // 已经播放的位置,以ms为单位
  134 + updateTime: new Date().getTime(), // 应用更新当前位置时的时间戳,以ms为单位
  135 + },
  136 + duration: totalDuration * 1000,
  137 + speed: 1.0, // 可选,默认是1.0,播放的倍速,按照应用内支持的speed进行设置,系统不做校验
  138 + bufferedTime: totalDuration * 1000, // 可选,资源缓存的时间,以ms为单位
  139 + };
  140 + this.lastSession?.setAVPlaybackState(playbackState, (err) => {
  141 + if (err) {
  142 + Logger.error(TAG, `Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
  143 + } else {
  144 + Logger.debug(TAG, `SetAVPlaybackState successfully`);
  145 + }
  146 + });
  147 + }
  148 +
  149 + // 置灰或禁用不支持的按钮
  150 + stopUseFeatures() {
  151 + // 取消指定session下的相关监听
  152 + // this.lastSession?.off('playFromAssetId');
  153 + // this.lastSession?.off('setSpeed');
  154 + // this.lastSession?.off('setLoopMode');
  155 + // this.lastSession?.off('toggleFavorite');
  156 + // this.lastSession?.off('skipToQueueItem');
  157 + // this.lastSession?.off('handleKeyEvent');
  158 + // this.lastSession?.off('commonCommand');
  159 + // this.lastSession?.off('rewind');
  160 + // this.lastSession?.off('fastForward');
  161 + }
  162 +
  163 + listenPlayEvents() {
  164 + this.lastSession?.on('play', () => {
  165 + Logger.debug(TAG, `on play `);
  166 + this.avplayerController?.play()
  167 + this.hasSetupProgress = false
  168 + });
  169 + this.lastSession?.on('pause', () => {
  170 + Logger.debug(TAG, `on pause `);
  171 + this.avplayerController?.pause()
  172 + });
  173 + this.lastSession?.on('stop', () => {
  174 + Logger.debug(TAG, `on stop `);
  175 + this.avplayerController?.stop()
  176 + });
  177 + // this.lastSession?.on('playNext', () => {
  178 + // Logger.debug(TAG, `on playNext `);
  179 + // });
  180 + // this.lastSession?.on('playPrevious', () => {
  181 + // Logger.debug(TAG, `on playPrevious `);
  182 + // });
  183 + this.lastSession?.on('seek', (position: number) => {
  184 + Logger.debug(TAG, `on seek , the time is ${JSON.stringify(position)}`);
  185 +
  186 + // 由于应用内seek可能会触发较长的缓冲等待,可以先把状态设置为 Buffering
  187 + let playbackState: AVSessionManager.AVPlaybackState = {
  188 + state: AVSessionManager.PlaybackState.PLAYBACK_STATE_BUFFERING, // 缓冲状态
  189 + };
  190 + this.lastSession?.setAVPlaybackState(playbackState, (err) => {
  191 + if (err) {
  192 + Logger.debug(TAG, `Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
  193 + } else {
  194 + Logger.debug(TAG, `SetAVPlaybackState seek buffering`);
  195 + }
  196 + });
  197 +
  198 + // 应用响应seek命令,使用应用内播放器完成seek实现
  199 + this.avplayerController?.setSeekTime(position * 0.001, SliderChangeMode.End)
  200 + this.hasSetupProgress = false
  201 + });
  202 + }
  203 +
  204 + // 开启后台长时任务
  205 + startContinuousTask() {
  206 + if (this.applyedLongTaskPlay) {
  207 + return
  208 + }
  209 + let params: Record<string, string> = {
  210 + "title": "开始后台任务",
  211 + "content": "内容?",
  212 + // "pushLink": pushLink,
  213 + // "taskid": message.taskId,
  214 + // "messageId": message.messageId,
  215 + // "gtTransmitMsgLocalNotify": "1",
  216 + }
  217 +
  218 + let wantAgentInfo: wantAgent.WantAgentInfo = {
  219 + // 点击通知后,将要执行的动作列表
  220 + // 添加需要被拉起应用的bundleName和abilityName
  221 + wants: [
  222 + {
  223 + deviceId: '',
  224 + bundleName: "com.peopledailychina.hosactivity",
  225 + abilityName: "EntryAbility",
  226 + // action: 'com.test.pushaction',
  227 + // entities: [],
  228 + // parameters:params,
  229 + }
  230 + ],
  231 + // 指定点击通知栏消息后的动作是拉起ability
  232 + actionType: wantAgent.OperationType.START_ABILITY,
  233 + // 使用者自定义的一个私有值
  234 + requestCode: 0,
  235 + // 点击通知后,动作执行属性
  236 + wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG],
  237 + };
  238 +
  239 + // 通过wantAgent模块下getWantAgent方法获取WantAgent对象
  240 + wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
  241 + if (!this.gotContextFunc) { return }
  242 + backgroundTaskManager.startBackgroundRunning(this.gotContextFunc(),
  243 + backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK,
  244 + wantAgentObj).then(() => {
  245 + Logger.debug(TAG, `Succeeded in operationing startBackgroundRunning.`);
  246 + this.applyedLongTaskPlay = true
  247 + }).catch((err: BusinessError) => {
  248 + Logger.error(TAG, `Failed to operation startBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
  249 + });
  250 + });
  251 + }
  252 +
  253 + stopContinuousTask() {
  254 + if (!this.gotContextFunc) { return }
  255 + backgroundTaskManager.stopBackgroundRunning(this.gotContextFunc()).then(() => {
  256 + Logger.debug(TAG, `Succeeded in operationing stopBackgroundRunning.`);
  257 + }).catch((err: BusinessError) => {
  258 + Logger.error(TAG, `Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
  259 + });
  260 + this.applyedLongTaskPlay = false
  261 + }
  262 +
  263 +}
1 import media from '@ohos.multimedia.media'; 1 import media from '@ohos.multimedia.media';
2 import prompt from '@ohos.promptAction'; 2 import prompt from '@ohos.promptAction';
3 -import { Logger } from '../utils/Logger';  
4 import { PlayerConstants, AVPlayerStatus, Events } from '../constants/PlayerConstants'; 3 import { PlayerConstants, AVPlayerStatus, Events } from '../constants/PlayerConstants';
5 import { BusinessError } from '@ohos.base'; 4 import { BusinessError } from '@ohos.base';
6 import { TrackingPlay } from 'wdTracking/Index'; 5 import { TrackingPlay } from 'wdTracking/Index';
7 import { ParamType } from 'wdTracking/Index'; 6 import { ParamType } from 'wdTracking/Index';
8 -import { DateTimeUtils } from 'wdKit/Index'; 7 +import { DateTimeUtils, Logger } from 'wdKit/Index';
  8 +import { BackgroundAudioController } from './BackgroundAudioController';
9 9
10 interface obj { 10 interface obj {
11 loop: boolean 11 loop: boolean
12 } 12 }
13 13
  14 +const TAG = "WDPlayerController"
  15 +
14 @Observed 16 @Observed
15 export class WDPlayerController { 17 export class WDPlayerController {
16 private initPromise: Promise<void>; 18 private initPromise: Promise<void>;
@@ -41,7 +43,7 @@ export class WDPlayerController { @@ -41,7 +43,7 @@ export class WDPlayerController {
41 public xComponentController?: XComponentController 43 public xComponentController?: XComponentController
42 public videoWidth: number = 0 44 public videoWidth: number = 0
43 public videoHeight: number = 0 45 public videoHeight: number = 0
44 - 46 + public keepOnBackground = false
45 47
46 48
47 49
@@ -58,20 +60,20 @@ export class WDPlayerController { @@ -58,20 +60,20 @@ export class WDPlayerController {
58 */ 60 */
59 private createAVPlayer(): Promise<void> { 61 private createAVPlayer(): Promise<void> {
60 return new Promise((resolve, reject) => { 62 return new Promise((resolve, reject) => {
61 - Logger.error("开始创建") 63 + Logger.debug(TAG, "开始创建")
62 media.createAVPlayer().then((avPlayer) => { 64 media.createAVPlayer().then((avPlayer) => {
63 if (avPlayer) { 65 if (avPlayer) {
64 - Logger.error("创建完成1") 66 + Logger.debug(TAG, "创建完成1")
65 this.avPlayer = avPlayer; 67 this.avPlayer = avPlayer;
66 this.bindState(); 68 this.bindState();
67 resolve(); 69 resolve();
68 } else { 70 } else {
69 - Logger.error("创建完成0")  
70 - Logger.error('[PlayVideoModel] createAvPlayer fail!'); 71 + Logger.error(TAG, "创建完成0")
  72 + Logger.error(TAG, '[PlayVideoModel] createAvPlayer fail!');
71 reject(); 73 reject();
72 } 74 }
73 }).catch((error: BusinessError) => { 75 }).catch((error: BusinessError) => {
74 - console.error(`AVPlayer catchCallback, error message:${error.message}`); 76 + Logger.error(TAG, `AVPlayer catchCallback, error message:${error.message}`);
75 }); 77 });
76 ; 78 ;
77 }); 79 });
@@ -143,10 +145,10 @@ export class WDPlayerController { @@ -143,10 +145,10 @@ export class WDPlayerController {
143 this.avPlayer.release(); 145 this.avPlayer.release();
144 this.status = PlayerConstants.STATUS_STOP; 146 this.status = PlayerConstants.STATUS_STOP;
145 this.watchStatus(); 147 this.watchStatus();
146 - Logger.info('[PlayVideoModel] state released called') 148 + Logger.info(TAG, '[PlayVideoModel] state released called')
147 break; 149 break;
148 default: 150 default:
149 - Logger.info('[PlayVideoModel] unKnown state: ' + state); 151 + Logger.info(TAG, '[PlayVideoModel] unKnown state: ' + state);
150 break; 152 break;
151 } 153 }
152 }); 154 });
@@ -155,7 +157,7 @@ export class WDPlayerController { @@ -155,7 +157,7 @@ export class WDPlayerController {
155 }); 157 });
156 this.avPlayer?.on(Events.ERROR, (error) => { 158 this.avPlayer?.on(Events.ERROR, (error) => {
157 this.playError(error.message); 159 this.playError(error.message);
158 - console.log('播放错误',JSON.stringify(error)) 160 + Logger.error(TAG, '播放错误' + JSON.stringify(error))
159 TrackingPlay.videoPlayError(error.message, this.pageName, this.pageName, this.pageParam) 161 TrackingPlay.videoPlayError(error.message, this.pageName, this.pageName, this.pageParam)
160 }) 162 })
161 this.avPlayer?.on('seekDone', (time: number) => { 163 this.avPlayer?.on('seekDone', (time: number) => {
@@ -186,7 +188,7 @@ export class WDPlayerController { @@ -186,7 +188,7 @@ export class WDPlayerController {
186 this.url = url; 188 this.url = url;
187 //加载时长prepareTime 189 //加载时长prepareTime
188 this.creatStartTime = DateTimeUtils.getTimeStamp() 190 this.creatStartTime = DateTimeUtils.getTimeStamp()
189 - console.log('开始创建',JSON.stringify(this.creatStartTime)) 191 + Logger.debug(TAG, '开始创建' + JSON.stringify(this.creatStartTime))
190 this.prepareTime = 0 192 this.prepareTime = 0
191 if(pageName){ 193 if(pageName){
192 this.pageName = pageName 194 this.pageName = pageName
@@ -195,7 +197,7 @@ export class WDPlayerController { @@ -195,7 +197,7 @@ export class WDPlayerController {
195 this.pageParam = pageParam 197 this.pageParam = pageParam
196 } 198 }
197 if (this.avPlayer == null) { 199 if (this.avPlayer == null) {
198 - console.log("等待") 200 + Logger.debug(TAG, "等待")
199 this.initPromise = this.createAVPlayer(); 201 this.initPromise = this.createAVPlayer();
200 await this.initPromise; 202 await this.initPromise;
201 } else { 203 } else {
@@ -209,13 +211,13 @@ export class WDPlayerController { @@ -209,13 +211,13 @@ export class WDPlayerController {
209 if (this.avPlayer == null) { 211 if (this.avPlayer == null) {
210 return 212 return
211 } 213 }
212 - console.log("开始播放", this.url) 214 + Logger.debug(TAG, "开始播放", this.url)
213 this.avPlayer.url = this.url; 215 this.avPlayer.url = this.url;
214 //加载时长prepareTime 216 //加载时长prepareTime
215 this.creatEndTime = DateTimeUtils.getTimeStamp() 217 this.creatEndTime = DateTimeUtils.getTimeStamp()
216 this.prepareTime = 0 218 this.prepareTime = 0
217 this.prepareTime = Math.floor((this.creatEndTime - this.creatStartTime)/1000) 219 this.prepareTime = Math.floor((this.creatEndTime - this.creatStartTime)/1000)
218 - console.log('开始播放2',JSON.stringify(this.prepareTime)) 220 + Logger.debug(TAG, '开始播放2',JSON.stringify(this.prepareTime))
219 } 221 }
220 222
221 async release() { 223 async release() {
@@ -237,6 +239,7 @@ export class WDPlayerController { @@ -237,6 +239,7 @@ export class WDPlayerController {
237 // if (this.avPlayer == null) { 239 // if (this.avPlayer == null) {
238 // return 240 // return
239 // } 241 // }
  242 + Logger.debug(TAG, "start pause")
240 this.avPlayer?.pause(); 243 this.avPlayer?.pause();
241 } 244 }
242 245
@@ -253,7 +256,7 @@ export class WDPlayerController { @@ -253,7 +256,7 @@ export class WDPlayerController {
253 async startRenderFrame(cb: Function) { 256 async startRenderFrame(cb: Function) {
254 this.avPlayer?.on('startRenderFrame', () => { 257 this.avPlayer?.on('startRenderFrame', () => {
255 cb && cb(); 258 cb && cb();
256 - console.info('startRenderFrame success') 259 + Logger.debug(TAG, 'startRenderFrame success')
257 }) 260 })
258 } 261 }
259 262
@@ -299,6 +302,7 @@ export class WDPlayerController { @@ -299,6 +302,7 @@ export class WDPlayerController {
299 // return 302 // return
300 // } 303 // }
301 if (this.status === PlayerConstants.STATUS_START) { 304 if (this.status === PlayerConstants.STATUS_START) {
  305 + Logger.debug(TAG, "start pause 1111")
302 this.avPlayer?.pause(); 306 this.avPlayer?.pause();
303 } else { 307 } else {
304 this.avPlayer?.play(); 308 this.avPlayer?.play();
@@ -349,6 +353,11 @@ export class WDPlayerController { @@ -349,6 +353,11 @@ export class WDPlayerController {
349 this.currentPlayTime=Math.floor(time / 1000); 353 this.currentPlayTime=Math.floor(time / 1000);
350 let nowSeconds = Math.floor(time / 1000); 354 let nowSeconds = Math.floor(time / 1000);
351 let totalSeconds = Math.floor(this.duration / 1000); 355 let totalSeconds = Math.floor(this.duration / 1000);
  356 +
  357 + if (this.keepOnBackground) {
  358 + BackgroundAudioController.sharedController().setSessionPlayProgress(time, this.duration)
  359 + }
  360 +
352 if (this.onTimeUpdate) { 361 if (this.onTimeUpdate) {
353 this.onTimeUpdate(time, this.duration); 362 this.onTimeUpdate(time, this.duration);
354 } 363 }
@@ -397,7 +406,7 @@ export class WDPlayerController { @@ -397,7 +406,7 @@ export class WDPlayerController {
397 if (this.onVolumeUpdate) { 406 if (this.onVolumeUpdate) {
398 this.onVolumeUpdate(this.volume); 407 this.onVolumeUpdate(this.volume);
399 } 408 }
400 - console.log("volume : " + this.volume) 409 + Logger.debug(TAG, "volume : " + this.volume)
401 } 410 }
402 411
403 onBrightActionUpdate(event: GestureEvent) { 412 onBrightActionUpdate(event: GestureEvent) {
@@ -421,22 +430,27 @@ export class WDPlayerController { @@ -421,22 +430,27 @@ export class WDPlayerController {
421 } 430 }
422 431
423 watchStatus() { 432 watchStatus() {
424 - console.log('watchStatus', this.status) 433 +
  434 + if (this.keepOnBackground) {
  435 + BackgroundAudioController.sharedController().setSessionPlayStatus(this.status)
  436 + }
  437 +
  438 + Logger.debug(TAG, 'watchStatus ' + this.status)
425 if(this.status == PlayerConstants.STATUS_START){ 439 if(this.status == PlayerConstants.STATUS_START){
426 - console.log('播放视频')  
427 - console.log('播放视频prepareTime',JSON.stringify(this.prepareTime))  
428 - console.log('播放视频pageName',JSON.stringify(this.pageName))  
429 - console.log('播放视频pageParam',JSON.stringify(this.pageParam)) 440 + Logger.debug(TAG, '播放视频')
  441 + Logger.debug(TAG, '播放视频prepareTime' + JSON.stringify(this.prepareTime))
  442 + Logger.debug(TAG, '播放视频pageName' + JSON.stringify(this.pageName))
  443 + Logger.debug(TAG, '播放视频pageParam' + JSON.stringify(this.pageParam))
430 // 播放埋点 444 // 播放埋点
431 TrackingPlay.videoPositivePlay(Number(this.prepareTime),this.pageName, this.pageName, this.pageParam) 445 TrackingPlay.videoPositivePlay(Number(this.prepareTime),this.pageName, this.pageName, this.pageParam)
432 } 446 }
433 if(this.status == PlayerConstants.STATUS_COMPLETION){ 447 if(this.status == PlayerConstants.STATUS_COMPLETION){
434 let initDuration = Math.floor(Number(this.duration)/1000) 448 let initDuration = Math.floor(Number(this.duration)/1000)
435 - console.log('播放结束')  
436 - console.log('播放结束currentPlayTime',JSON.stringify(this.currentPlayTime))  
437 - console.log('播放结束initDuration',JSON.stringify(initDuration))  
438 - console.log('播放结束pageName',JSON.stringify(this.pageName))  
439 - console.log('播放结束pageParam',JSON.stringify(this.pageParam)) 449 + Logger.debug(TAG, '播放结束')
  450 + Logger.debug(TAG, '播放结束currentPlayTime' + JSON.stringify(this.currentPlayTime))
  451 + Logger.debug(TAG, '播放结束initDuration' + JSON.stringify(initDuration))
  452 + Logger.debug(TAG, '播放结束pageName' + JSON.stringify(this.pageName))
  453 + Logger.debug(TAG, '播放结束pageParam' + JSON.stringify(this.pageParam))
440 // 播放结束埋点 454 // 播放结束埋点
441 TrackingPlay.videoPlayEnd(this.currentPlayTime, initDuration, this.currentPlayTime, this.pageName, this.pageName, this.pageParam) 455 TrackingPlay.videoPlayEnd(this.currentPlayTime, initDuration, this.currentPlayTime, this.pageName, this.pageName, this.pageParam)
442 } 456 }
@@ -88,7 +88,8 @@ struct Index { @@ -88,7 +88,8 @@ struct Index {
88 88
89 onPageHide() { 89 onPageHide() {
90 // this.status = PlayerConstants.STATUS_PAUSE; 90 // this.status = PlayerConstants.STATUS_PAUSE;
91 - this.AudioSuspension.playerController.get()?.pause(); 91 + console.info('onPageHide');
  92 + // this.AudioSuspension.playerController.get()?.pause();
92 } 93 }
93 94
94 build() { 95 build() {
@@ -22,6 +22,7 @@ import { webview } from '@kit.ArkWeb' @@ -22,6 +22,7 @@ import { webview } from '@kit.ArkWeb'
22 import { NewspaperWidgetCommon } from '../dailynewspaperwidget/common/NewspaperWidgetCommon' 22 import { NewspaperWidgetCommon } from '../dailynewspaperwidget/common/NewspaperWidgetCommon'
23 import { LiveRoomManager } from 'wdDetailPlayLive/Index' 23 import { LiveRoomManager } from 'wdDetailPlayLive/Index'
24 import { initGlobalPlayerSettings } from 'wdPlayer/src/main/ets/utils/GlobalSetting' 24 import { initGlobalPlayerSettings } from 'wdPlayer/src/main/ets/utils/GlobalSetting'
  25 +import { BackgroundAudioController } from 'wdPlayer/Index'
25 26
26 const TAG = "[StartupManager]" 27 const TAG = "[StartupManager]"
27 28
@@ -115,6 +116,8 @@ export class StartupManager { @@ -115,6 +116,8 @@ export class StartupManager {
115 this.initAuthLogin() 116 this.initAuthLogin()
116 117
117 this.initLiveChatRoom() 118 this.initLiveChatRoom()
  119 +
  120 + this.initBackgroundAudioTask()
118 Logger.debug(TAG, "App 必要初始化完成") 121 Logger.debug(TAG, "App 必要初始化完成")
119 } 122 }
120 123
@@ -210,6 +213,12 @@ export class StartupManager { @@ -210,6 +213,12 @@ export class StartupManager {
210 } 213 }
211 } 214 }
212 215
  216 + private initBackgroundAudioTask() {
  217 + BackgroundAudioController.sharedController().gotContextFunc = () => {
  218 + return StartupManager.sharedInstance().context!
  219 + }
  220 + }
  221 +
213 private initThirdPlatformSDK() { 222 private initThirdPlatformSDK() {
214 223
215 } 224 }
@@ -24,6 +24,9 @@ @@ -24,6 +24,9 @@
24 "startWindowIcon": "$media:app_icon", 24 "startWindowIcon": "$media:app_icon",
25 "startWindowBackground": "$color:start_window_background", 25 "startWindowBackground": "$color:start_window_background",
26 "exported": true, 26 "exported": true,
  27 + "backgroundModes": [
  28 + "audioPlayback"
  29 + ],
27 "skills": [ 30 "skills": [
28 { 31 {
29 "entities": [ 32 "entities": [
@@ -33,6 +36,8 @@ @@ -33,6 +36,8 @@
33 "action.system.home", 36 "action.system.home",
34 "com.test.pushaction" 37 "com.test.pushaction"
35 ], 38 ],
  39 + },
  40 + {
36 "uris": [ 41 "uris": [
37 { 42 {
38 "scheme": 'rmrbapp', 43 "scheme": 'rmrbapp',
@@ -40,7 +45,7 @@ @@ -40,7 +45,7 @@
40 'port': '8080', 45 'port': '8080',
41 "path": 'openwith' 46 "path": 'openwith'
42 } 47 }
43 - ] 48 + ],
44 } 49 }
45 ] 50 ]
46 } 51 }
@@ -103,6 +108,16 @@ @@ -103,6 +108,16 @@
103 } 108 }
104 }, 109 },
105 { 110 {
  111 + "name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
  112 + "reason": "$string:permission_background_audio",
  113 + "usedScene": {
  114 + "abilities": [
  115 + "FormAbility"
  116 + ],
  117 + "when": "always"
  118 + }
  119 + },
  120 + {
106 "name": "ohos.permission.INTERNET" 121 "name": "ohos.permission.INTERNET"
107 }, 122 },
108 ], 123 ],
@@ -29,6 +29,10 @@ @@ -29,6 +29,10 @@
29 "value": "开启之后即可用于收录声音进行拍摄视频、语音搜索、语音评论功能," 29 "value": "开启之后即可用于收录声音进行拍摄视频、语音搜索、语音评论功能,"
30 }, 30 },
31 { 31 {
  32 + "name": "permission_background_audio",
  33 + "value": "开启之后即可用于后台音频播放"
  34 + },
  35 + {
32 "name": "dialog_text_title", 36 "name": "dialog_text_title",
33 "value": "个人隐私保护指引" 37 "value": "个人隐私保护指引"
34 }, 38 },