• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 = 'PiPVideo';
21const TIMEOUT: number = 3000;
22
23@Styles
24function fillSize() {
25  .size({ width: '100%', height: '100%' })
26}
27
28@Entry
29@Component
30export struct PiPVideo {
31  xComponentId: string = 'pip';
32  windowType: PiPWindow.PiPTemplateType = PiPWindow.PiPTemplateType.VIDEO_PLAY;
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            VideoControl();
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
208
209@Component
210struct VideoControl {
211  @State isPlaying: boolean = true;
212  @State shouldShowNextAndPrev: boolean = true;
213  @State horMargin: number = 12;
214  @Consume hideControlDelay: boolean;
215
216  build() {
217    RelativeContainer() {
218      Button({ type: ButtonType.Circle }) {
219        Image(this.isPlaying ? $r('sys.media.ohos_ic_public_pause') : $r('sys.media.ohos_ic_public_play'))
220          .fillColor($r('sys.color.ohos_id_color_primary_contrary'))
221          .objectFit(ImageFit.Contain)
222      }
223      .backgroundColor('#00FFFFFF')
224      .size({ width: 24, height: 24 })
225      .margin({ top: 12, bottom: 12 })
226      .alignRules({
227        center: { anchor: '__container__', align: VerticalAlign.Center },
228        middle: { anchor: '__container__', align: HorizontalAlign.Center }
229      })
230      .id('control_play')
231      .responseRegion({ x: '-50%', y: '-50%', width: '200%', height: '200%' })
232      .onClick(() => {
233        this.isPlaying = !this.isPlaying;
234        this.hideControlDelay = true;
235        pip.triggerAction('playbackStateChanged');
236        console.debug(TAG, 'action: play or pause');
237      })
238
239      Button({ type: ButtonType.Circle }) {
240        Image($r('sys.media.ohos_ic_public_play_last'))
241          .fillColor($r('sys.color.ohos_id_color_primary_contrary'))
242          .objectFit(ImageFit.Contain)
243      }
244      .backgroundColor('#00FFFFFF')
245      .size({ width: 24, height: 24 })
246      .margin({ left: this.horMargin, right: this.horMargin * 2, top: 12, bottom: 12 })
247      .visibility(this.shouldShowNextAndPrev ? Visibility.Visible : Visibility.None)
248      .alignRules({
249        center: { anchor: '__container__', align: VerticalAlign.Center },
250        right: { anchor: 'control_play', align: HorizontalAlign.Start }
251      })
252      .id('control_play_last')
253      .responseRegion({ x: '-50%', y: '-50%', width: '200%', height: '200%' })
254      .onClick(() => {
255        this.hideControlDelay = true;
256        pip.triggerAction('previousVideo');
257        console.debug(TAG, 'action: play last');
258      })
259
260      Button({ type: ButtonType.Circle }) {
261        Image($r('sys.media.ohos_ic_public_play_next'))
262          .fillColor($r('sys.color.ohos_id_color_primary_contrary'))
263          .objectFit(ImageFit.Contain)
264      }
265      .backgroundColor('#00FFFFFF')
266      .size({ width: 24, height: 24 })
267      .margin({ left: this.horMargin * 2, right: this.horMargin, top: 12, bottom: 12 })
268      .visibility(this.shouldShowNextAndPrev ? Visibility.Visible : Visibility.None)
269      .alignRules({
270        center: { anchor: '__container__', align: VerticalAlign.Center },
271        left: { anchor: 'control_play', align: HorizontalAlign.End }
272      })
273      .id('control_play_next')
274      .responseRegion({ x: '-50%', y: '-50%', width: '200%', height: '200%' })
275      .onClick(() => {
276        this.hideControlDelay = true;
277        pip.triggerAction('nextVideo');
278        console.debug(TAG, 'action: play next');
279      })
280    }
281    .width('100%')
282    .height(48)
283    .linearGradient({
284      angle: 0,
285      colors: [['#30000000', 0.0], ['#00000000', 1.0]]
286    })
287    .onAreaChange((oldValue, newValue) => {
288      if (newValue.width < 104) {
289        this.shouldShowNextAndPrev = false;
290      } else if (newValue.width < 152) {
291        this.horMargin = (newValue.width as number - 104) / 4;
292        this.shouldShowNextAndPrev = true;
293      } else {
294        this.horMargin = 12;
295        this.shouldShowNextAndPrev = true;
296      }
297    })
298    .alignRules({
299      bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
300      left: { anchor: '__container__', align: HorizontalAlign.Start }
301    })
302    .id('video_control')
303  }
304}
305