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'; 19import window from '@ohos.window'; 20 21const TAG: string = 'PiPLive'; 22const TIMEOUT: number = 3000; 23 24@Styles 25function fillSize() { 26 .size({ width: '100%', height: '100%' }) 27} 28 29@Entry 30@Component 31export struct PiPLive { 32 xComponentId: string = 'pip'; 33 windowType: PiPWindow.PiPTemplateType = PiPWindow.PiPTemplateType.VIDEO_LIVE; 34 private hideEventId: number = -1; 35 @State private showControl: boolean = false; 36 private xComponentController: XComponentController = new XComponentController(); 37 private surfaceId: string = ''; 38 private controlTransEffect: TransitionEffect = TransitionEffect.OPACITY; 39 @Provide @Watch('onHideControlNow') hideControlNow: boolean = false; 40 @Provide @Watch('onHideControlDelay') hideControlDelay: boolean = false; 41 42 onHideControlNow() { 43 if (this.hideControlNow) { 44 this.switchToHideWithoutAnime(); 45 } 46 this.hideControlNow = false; 47 } 48 49 onHideControlDelay() { 50 if (this.hideControlDelay) { 51 this.delayHide(); 52 } 53 this.hideControlDelay = false; 54 } 55 56 switchToShow() { 57 animateTo({ curve: curves.responsiveSpringMotion(0.25, 1) }, () => { 58 this.showControl = true; 59 }); 60 this.delayHide(); 61 } 62 63 switchToHide() { 64 if (this.hideEventId !== -1) { 65 clearTimeout(this.hideEventId); 66 } 67 animateTo({ curve: curves.responsiveSpringMotion(0.25, 1) }, () => { 68 this.showControl = false; 69 }); 70 } 71 72 switchToHideWithoutAnime() { 73 if (this.hideEventId !== -1) { 74 clearTimeout(this.hideEventId); 75 } 76 this.showControl = false; 77 } 78 79 delayHide() { 80 if (this.hideEventId !== -1) { 81 clearTimeout(this.hideEventId); 82 } 83 this.hideEventId = this.showControl ? 84 setTimeout(() => { 85 animateTo({ curve: curves.responsiveSpringMotion(0.25, 1) }, () => { 86 this.showControl = false; 87 }); 88 }, TIMEOUT) : 89 -1; 90 } 91 92 build() { 93 Stack() { 94 XComponent({ id: this.xComponentId, type: 'surface', controller: this.xComponentController }) 95 .onLoad(() => { 96 pip.initXComponentController(this.xComponentController); 97 console.debug(TAG, 'XComponent onLoad done'); 98 }) 99 .fillSize(); 100 101 RelativeContainer() { 102 Stack() 103 .fillSize() 104 .id('fill_stack') 105 if (this.showControl) { 106 RelativeContainer() { 107 DefaultControl(); 108 LiveControl(); 109 } 110 .fillSize() 111 .transition(this.controlTransEffect) 112 .alignRules({ 113 top: { anchor: '__container__', align: VerticalAlign.Top }, 114 right: { anchor: '__container__', align: HorizontalAlign.End } 115 }) 116 .id('control_inner') 117 } 118 } 119 .fillSize() 120 .id('control') 121 .gesture( 122 GestureGroup(GestureMode.Exclusive, 123 TapGesture({ count: 2 }) 124 .onAction((event: GestureEvent) => { 125 this.switchToHideWithoutAnime(); 126 pip.processScale(); 127 }), 128 TapGesture({ count: 1 }) 129 .onAction((event: GestureEvent) => { 130 if (this.showControl) { 131 this.switchToHide(); 132 } else { 133 this.switchToShow(); 134 } 135 }), 136 PanGesture() 137 .onActionStart((event: GestureEvent) => { 138 this.switchToHide(); 139 pip.startMove(); 140 }) 141 ) 142 ) 143 } 144 .fillSize() 145 } 146} 147 148@Component 149struct DefaultControl { 150 @Consume hideControlNow: boolean; 151 152 build() { 153 RelativeContainer() { 154 Button({ type: ButtonType.Circle }) { 155 Image($r('sys.media.ohos_ic_public_close')) 156 .size({ width: 24, height: 24 }) 157 .fillColor($r('sys.color.ohos_id_color_primary_contrary')) 158 .objectFit(ImageFit.Contain) 159 } 160 .backgroundColor('#00FFFFFF') 161 .size({ width: 24, height: 24 }) 162 .margin(12) 163 .alignRules({ 164 center: { anchor: '__container__', align: VerticalAlign.Center }, 165 left: { anchor: '__container__', align: HorizontalAlign.Start } 166 }) 167 .id('control_exit') 168 .responseRegion({ x: '-50%', y: '-50%', width: '200%', height: '200%' }) 169 .onClick(() => { 170 this.hideControlNow = true; 171 pip.close(); 172 console.debug(TAG, 'action: exit'); 173 }) 174 175 Button({ type: ButtonType.Circle }) { 176 Image($r('sys.media.ohos_ic_public_restore')) 177 .fillColor($r('sys.color.ohos_id_color_primary_contrary')) 178 .objectFit(ImageFit.Contain) 179 } 180 .backgroundColor('#00FFFFFF') 181 .size({ width: 24, height: 24 }) 182 .margin(12) 183 .alignRules({ 184 center: { anchor: '__container__', align: VerticalAlign.Center }, 185 right: { anchor: '__container__', align: HorizontalAlign.End } 186 }) 187 .id('control_restore') 188 .responseRegion({ x: '-50%', y: '-50%', width: '200%', height: '200%' }) 189 .onClick(() => { 190 this.hideControlNow = true; 191 pip.restore(); 192 console.debug(TAG, 'action: restore'); 193 }) 194 } 195 .width('100%') 196 .height(48) 197 .linearGradient({ 198 angle: 180, 199 colors: [['#30000000', 0.0], ['#00000000', 1.0]] 200 }) 201 .alignRules({ 202 top: { anchor: '__container__', align: VerticalAlign.Top }, 203 left: { anchor: '__container__', align: HorizontalAlign.Start } 204 }) 205 .id('default_control') 206 } 207} 208 209@Component 210struct LiveControl { 211 @State isPlaying: boolean = true; 212 @Consume hideControlDelay: boolean; 213 214 build() { 215 RelativeContainer() { 216 Button({ type: ButtonType.Circle }) { 217 Image(this.isPlaying ? $r('sys.media.ohos_ic_public_pause') : $r('sys.media.ohos_ic_public_play')) 218 .fillColor($r('sys.color.ohos_id_color_primary_contrary')) 219 .objectFit(ImageFit.Contain) 220 } 221 .backgroundColor('#00FFFFFF') 222 .size({ width: 24, height: 24 }) 223 .margin(12) 224 .alignRules({ 225 top: { anchor: '__container__', align: VerticalAlign.Top }, 226 middle: { anchor: '__container__', align: HorizontalAlign.Center } 227 }) 228 .id('control_play') 229 .responseRegion({ x: '-50%', y: '-50%', width: '200%', height: '200%' }) 230 .onClick(() => { 231 this.isPlaying = !this.isPlaying; 232 this.hideControlDelay = true; 233 pip.triggerAction('playbackStateChanged'); 234 console.debug(TAG, 'action: live play'); 235 }) 236 237 Progress({ value: 0, total: 100, type: ProgressType.Linear }) 238 .size({ width: '100%', height: 4 }) 239 .color('#33EEEEEE') 240 .padding({ left: 16, right: 16 }) 241 .margin({ top: -4, bottom: 8 }) 242 .alignRules({ 243 bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, 244 middle: { anchor: '__container__', align: HorizontalAlign.Center } 245 }) 246 .id('control_progress') 247 } 248 .width('100%') 249 .height(56) 250 .linearGradient({ 251 angle: 0, 252 colors: [['#30000000', 0.0], ['#00000000', 1.0]] 253 }) 254 .alignRules({ 255 bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, 256 left: { anchor: '__container__', align: HorizontalAlign.Start } 257 }) 258 .id('live_control') 259 } 260} 261