1/* 2 * Copyright (c) 2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import curves from '@ohos.curves'; 17import PiPWindow from '@ohos.PiPWindow'; 18import pip from '@ohos.pip'; 19 20const TAG: string = 'PiPMeeting'; 21const TIMEOUT: number = 3000; 22 23@Styles 24function fillSize() { 25 .size({ width: '100%', height: '100%' }) 26} 27 28@Entry 29@Component 30export struct PiPMeeting { 31 xComponentId: string = 'pip'; 32 windowType: PiPWindow.PiPTemplateType = PiPWindow.PiPTemplateType.VIDEO_MEETING; 33 private hideEventId: number = -1; 34 @State private showControl: boolean = false; 35 private xComponentController: XComponentController = new XComponentController(); 36 private surfaceId: string = ''; 37 private controlTransEffect: TransitionEffect = TransitionEffect.OPACITY; 38 @Provide @Watch('onHideControlNow') hideControlNow: boolean = false; 39 @Provide @Watch('onHideControlDelay') hideControlDelay: boolean = false; 40 41 onHideControlNow() { 42 if (this.hideControlNow) { 43 this.switchToHideWithoutAnime(); 44 } 45 this.hideControlNow = false; 46 } 47 48 onHideControlDelay() { 49 if (this.hideControlDelay) { 50 this.delayHide(); 51 } 52 this.hideControlDelay = false; 53 } 54 55 switchToShow() { 56 animateTo({ curve: curves.responsiveSpringMotion(0.25, 1) }, () => { 57 this.showControl = true; 58 }); 59 this.delayHide(); 60 } 61 62 switchToHide() { 63 if (this.hideEventId !== -1) { 64 clearTimeout(this.hideEventId); 65 } 66 animateTo({ curve: curves.responsiveSpringMotion(0.25, 1) }, () => { 67 this.showControl = false; 68 }); 69 } 70 71 switchToHideWithoutAnime() { 72 if (this.hideEventId !== -1) { 73 clearTimeout(this.hideEventId); 74 } 75 this.showControl = false; 76 } 77 78 delayHide() { 79 if (this.hideEventId !== -1) { 80 clearTimeout(this.hideEventId); 81 } 82 this.hideEventId = this.showControl ? 83 setTimeout(() => { 84 animateTo({ curve: curves.responsiveSpringMotion(0.25, 1) }, () => { 85 this.showControl = false; 86 }); 87 }, TIMEOUT) : 88 -1; 89 } 90 91 build() { 92 Stack() { 93 XComponent({ id: this.xComponentId, type: 'surface', controller: this.xComponentController }) 94 .onLoad(() => { 95 pip.initXComponentController(this.xComponentController); 96 console.debug(TAG, 'XComponent onLoad done'); 97 }) 98 .fillSize(); 99 100 RelativeContainer() { 101 Stack() 102 .fillSize() 103 .id('fill_stack') 104 if (this.showControl) { 105 RelativeContainer() { 106 DefaultControl(); 107 MeetingControl(); 108 } 109 .fillSize() 110 .transition(this.controlTransEffect) 111 .alignRules({ 112 top: { anchor: '__container__', align: VerticalAlign.Top }, 113 right: { anchor: '__container__', align: HorizontalAlign.End } 114 }) 115 .id('control_inner') 116 } 117 } 118 .fillSize() 119 .id('control') 120 .gesture( 121 GestureGroup(GestureMode.Exclusive, 122 TapGesture({ count: 2 }) 123 .onAction((event: GestureEvent) => { 124 this.switchToHideWithoutAnime(); 125 pip.processScale(); 126 }), 127 TapGesture({ count: 1 }) 128 .onAction((event: GestureEvent) => { 129 if (this.showControl) { 130 this.switchToHide(); 131 } else { 132 this.switchToShow(); 133 } 134 }), 135 PanGesture() 136 .onActionStart((event: GestureEvent) => { 137 this.switchToHide(); 138 pip.startMove(); 139 }) 140 ) 141 ) 142 } 143 .fillSize() 144 } 145} 146 147@Component 148struct DefaultControl { 149 @Consume hideControlNow: boolean; 150 151 build() { 152 RelativeContainer() { 153 Button({ type: ButtonType.Circle }) { 154 Image($r('sys.media.ohos_ic_public_close')) 155 .size({ width: 24, height: 24 }) 156 .fillColor($r('sys.color.ohos_id_color_primary_contrary')) 157 .objectFit(ImageFit.Contain) 158 } 159 .backgroundColor('#00FFFFFF') 160 .size({ width: 24, height: 24 }) 161 .margin(12) 162 .alignRules({ 163 center: { anchor: '__container__', align: VerticalAlign.Center }, 164 left: { anchor: '__container__', align: HorizontalAlign.Start } 165 }) 166 .id('control_exit') 167 .responseRegion({ x: '-50%', y: '-50%', width: '200%', height: '200%' }) 168 .onClick(() => { 169 this.hideControlNow = true; 170 pip.close(); 171 console.debug(TAG, 'action: exit'); 172 }) 173 174 Button({ type: ButtonType.Circle }) { 175 Image($r('sys.media.ohos_ic_public_restore')) 176 .fillColor($r('sys.color.ohos_id_color_primary_contrary')) 177 .objectFit(ImageFit.Contain) 178 } 179 .backgroundColor('#00FFFFFF') 180 .size({ width: 24, height: 24 }) 181 .margin(12) 182 .alignRules({ 183 center: { anchor: '__container__', align: VerticalAlign.Center }, 184 right: { anchor: '__container__', align: HorizontalAlign.End } 185 }) 186 .id('control_restore') 187 .responseRegion({ x: '-50%', y: '-50%', width: '200%', height: '200%' }) 188 .onClick(() => { 189 this.hideControlNow = true; 190 pip.restore(); 191 console.debug(TAG, 'action: restore'); 192 }) 193 } 194 .width('100%') 195 .height(48) 196 .linearGradient({ 197 angle: 180, 198 colors: [['#30000000', 0.0], ['#00000000', 1.0]] 199 }) 200 .alignRules({ 201 top: { anchor: '__container__', align: VerticalAlign.Top }, 202 left: { anchor: '__container__', align: HorizontalAlign.Start } 203 }) 204 .id('default_control') 205 } 206} 207 208const sizeArray = [1.0, 1.5]; 209 210@Component 211struct MeetingControl { 212 @State isMute: boolean = true; 213 @State isRecord: boolean = true; 214 @State defaultMargin: number = 8; 215 @State defaultSize: number = 12; 216 @State defaultBigSize: number = 24; 217 @State sizeIndex: number = 0; 218 @Consume hideControlDelay: boolean; 219 220 build() { 221 RelativeContainer() { 222 223 Button({ type: ButtonType.Circle }) { 224 Image($r('sys.media.ohos_ic_public_hang_up')) 225 .size({ width: 12 * sizeArray[this.sizeIndex], height: 12 * sizeArray[this.sizeIndex] }) 226 .fillColor($r('sys.color.ohos_id_color_primary_contrary')) 227 .objectFit(ImageFit.Contain) 228 } 229 .backgroundColor($r('sys.color.ohos_id_color_handup')) 230 .size({ width: 24 * sizeArray[this.sizeIndex], height: 24 * sizeArray[this.sizeIndex] }) 231 .margin({ top: 12, bottom: 12 }) 232 .alignRules({ 233 bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, 234 middle: { anchor: '__container__', align: HorizontalAlign.Center } 235 }) 236 .id('control_hangup') 237 .onClick(() => { 238 this.hideControlDelay = true; 239 pip.triggerAction('hangUp'); 240 console.debug(TAG, 'action: hangup'); 241 }) 242 243 Button({ type: ButtonType.Circle }) { 244 Image(this.isMute ? $r('sys.media.ohos_ic_public_sound_off') : $r('sys.media.ohos_ic_public_sound')) 245 .size({ width: 8 * sizeArray[this.sizeIndex], height: 8 * sizeArray[this.sizeIndex] }) 246 .fillColor($r('sys.color.ohos_id_color_primary')) 247 .objectFit(ImageFit.Contain) 248 } 249 .backgroundColor($r('sys.color.ohos_id_color_floating_button_icon')) 250 .size({ width: 16 * sizeArray[this.sizeIndex], height: 16 * sizeArray[this.sizeIndex] }) 251 .margin({ left: 8 * sizeArray[this.sizeIndex], right: 8 * sizeArray[this.sizeIndex] }) 252 .alignRules({ 253 center: { anchor: 'control_hangup', align: VerticalAlign.Center }, 254 right: { anchor: 'control_hangup', align: HorizontalAlign.Start } 255 }) 256 .id('control_mute') 257 .onClick(() => { 258 this.hideControlDelay = true; 259 this.isMute = !this.isMute; 260 pip.triggerAction('voiceStateChanged'); 261 console.debug(TAG, 'action: voice enable or disable'); 262 }) 263 264 Button({ type: ButtonType.Circle }) { 265 Image(this.isRecord ? $r('sys.media.ohos_ic_public_video') : $r('sys.media.ohos_ic_public_video_off')) 266 .size({ width: 8 * sizeArray[this.sizeIndex], height: 8 * sizeArray[this.sizeIndex] }) 267 .fillColor($r('sys.color.ohos_id_color_primary')) 268 .objectFit(ImageFit.Contain) 269 } 270 .backgroundColor($r('sys.color.ohos_id_color_floating_button_icon')) 271 .size({ width: 16 * sizeArray[this.sizeIndex], height: 16 * sizeArray[this.sizeIndex] }) 272 .margin({ left: 8 * sizeArray[this.sizeIndex], right: 8 * sizeArray[this.sizeIndex] }) 273 .alignRules({ 274 center: { anchor: 'control_hangup', align: VerticalAlign.Center }, 275 left: { anchor: 'control_hangup', align: HorizontalAlign.End } 276 }) 277 .id('control_record') 278 .onClick(() => { 279 this.hideControlDelay = true; 280 this.isRecord = !this.isRecord; 281 pip.triggerAction('videoStateChanged'); 282 console.debug(TAG, 'action: video enable or disable'); 283 }) 284 } 285 .width('100%') 286 .height(48) 287 .onAreaChange((oldValue: Area, newValue: Area) => { 288 if (oldValue.width == newValue.width) { 289 return; 290 } 291 this.sizeIndex = newValue.width >= 150 ? 1 : 0; 292 }) 293 .alignRules({ 294 bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, 295 left: { anchor: '__container__', align: HorizontalAlign.Start } 296 }) 297 .id('meeting_control') 298 } 299} 300 301