ContentChildren始终为空,因此永远不会调用相交观察器

时间:2019-05-14 18:44:01

标签: javascript angular typescript ionic-framework intersection-observer

我正在将项目从Ionic 3迁移到Ionic 4,但我注意到在Ionic 3中可以使用的自动播放视频功能在Ionic 4中不再可用。

根据我的判断,尽管视图中显示了视频,但并未填充ContentChildren。我也尝试过使用forwardRef,但这没做任何事情。

我检查了其他与此类似的问题,但所提供的解决方案均不适用于我,并且大多数都是低质量的答案,因此考虑到自解决方案以来已经过去的时间,我认为这不是重复的被问到。

我正在使用的重要软件包是: 角度:7.2.15,@离子/角度4.4.0,zone.js 0.8.29,交点观察者0.7.0

这是自动播放组件

import { Component, ContentChildren, ElementRef, forwardRef, NgZone, OnDestroy, OnInit, QueryList } from '@angular/core';
import { AutoplayVideoDirective } from '../../../directives/autoplay-video.directive';

@Component({
  selector: 'autoplay',
  template: `<ng-content></ng-content>`
})
export class AutoplayContentComponent implements OnInit, OnDestroy {

  @ContentChildren(forwardRef(() => AutoplayVideoDirective),
    {
      read: ElementRef,
      descendants: true,
    },
  ) autoPlayVideoRefs: QueryList<any>;

  private intersectionObserver: IntersectionObserver;
  private mutationObserver: MutationObserver;

  private play: Promise<any>;

  constructor(private element: ElementRef,
              public ngZone: NgZone) {}

  public ngOnInit() {

    // we can run this outside the ngZone, no need to trigger change detection
    this.ngZone.runOutsideAngular(() => {
      this.intersectionObserver = this.getIntersectionObserver();
      this.mutationObserver = this.getMutationObserver(this.element.nativeElement);
    });
  }

  // clean things ups
  public ngOnDestroy() {
    if (this.intersectionObserver) {
      this.intersectionObserver.disconnect();
    }
    if (this.mutationObserver) {
      this.mutationObserver.disconnect();
    }
  }

  // construct the InterSectionObserver and return it
  private getIntersectionObserver() {
    // execute the onIntersection on the threshold intersection of 0 and 70%
    return new IntersectionObserver(entries => this.onIntersection(entries), {
      threshold: [0, 0.70],
    });
  }

  // construct the MutationObserver and return it
  private getMutationObserver(containerElement: HTMLElement) {

    console.log(containerElement);

    // execute the onDomChange
    let mutationObserver = new MutationObserver(() => this.onDomChange());

    // at the very least, childList, attributes, or characterData
    // must be set to true
    const config = {attributes: true, characterData: true, childList: true};

    // attach the mutation observer to the container element
    // and start observing it
    mutationObserver.observe(containerElement, config);

    return mutationObserver;

  }

  private onDomChange() {
    // when the DOM changes, loop over each element
    // we want to observe for its interaction,
    // and do observe it

    console.log(this.autoPlayVideoRefs);

    this.autoPlayVideoRefs.forEach((video: ElementRef) => {
      this.checkIfVideosCanLoad(video.nativeElement).then((canPlay) => {
        console.log('Video can play: ', canPlay);
      });

      this.intersectionObserver.observe(video.nativeElement);
    });
  }

  /*
   * In low-power mode, videos do not load.
   * So this quickly checks to see if videos have the capability of loading.
   */
  private async checkIfVideosCanLoad(video: any) {
    let canPlay: boolean;

    return new Promise((resolve) => {
      // A safe timeout of 3 seconds, before we declare that the phone is in low power mode.
      let timeout = setTimeout(() => {
        canPlay = false;
        resolve(canPlay);
      }, 3000);

      // Loads meta data about the video, but not the whole video itself.
      video.onloadeddata = () => {
        canPlay = true;
        clearTimeout(timeout);
        resolve(canPlay);
      };
    });
  }

  private onIntersection(entries: IntersectionObserverEntry[]) {
    entries.forEach((entry: any) => {

      // get the video element
      let video = entry.target;

      // are we intersecting?
      if (!entry.isIntersecting) return;

      // play the video if we passed the threshold
      // of 0.7 and store the promise so we can safely
      // pause it again
      if (entry.intersectionRatio >= 0.70) {
        if (this.play === undefined) this.play = video.play();

      } else if (entry.intersectionRatio < 0.70) {

        // no need to pause something if it didn't start playing yet.
        if (this.play !== undefined) {

          // wait for the promise to resolve, then pause the video
          this.play.then(() => {
            video.pause();
            this.play = undefined;
          }).catch(() => {});
        }
      }
    });
  }
}

自动播放指令非常简单。

import { Directive } from '@angular/core';

/*
 * To be used with the autoplay-content.ts component.
 */
@Directive({
  selector: 'video'
})
export class AutoplayVideoDirective {}

与包裹自动播放组件的组件相同。这称为inline-video

<autoplay>
  <div tappable (tap)="changeVideoAudio(video?.id)">
    <video video playsinline loop [muted]="'muted'" [autoplay]="true" preload="auto" muted="muted"
           [poster]="(video?.src | AspectRatio) | videoPoster" [id]="'media-' + video?.id" class="video-media">
      <source [src]="video.src | AspectRatio" type="video/mp4" src="">
    </video>
  </div>
</autoplay>

我不确定为什么在Ionic 4中不起作用,但是在Ionic 3中...所以任何帮助将不胜感激。

组件的层次结构如下:

Feed Page > Feed Item > Autoplay > Inline Videoautoplay组件包裹在inline video组件周围。

编辑:我在config.xml

中也设置了AllowInlineMediaPlayback首选项

<preference name="AllowInlineMediaPlayback" value="true" />

0 个答案:

没有答案