Angular Ionic:视频标签中视频之间的无缝转换



我有一个Angular/Ionic(分别为版本10和版本5)组件,它以数组的形式获取播放列表。每个位置都是一个对象,包含关于视频的一些元数据和一个fileSrc,该文件针对我的应用程序存储中所述视频的位置。在我的模板中,我有一个这样的标签:

<video *ngIf="video" #videoElement controls (ended)="skipToNextVideoInQueue()"
[src]="video" type="video/mp4"

该组件有一个queuePosition公共变量,该变量从0开始设置播放列表的当前位置。视频结束时,skipToNextVideoInQueue()方法将被激发。这种方法,除其他外,可以执行以下操作:

this.queuePosition += 1;
this.video = this.playlist[queuePosition].fileSrc;
this.playVideo();

最后一个playVideo()方法只做以下操作:

setTimeout(() => {
const videoElement = this.videoElement.nativeElement;
if (currentVideo) {
currentVideo.play();
}
});

到目前为止,还可以接受:这种行为或多或少是我最初所期望的。播放列表从头开始,并不断播放下一个视频,直到播放列表结束。

现在唯一的问题是,在一个视频的(结束的)事件和下一个视频(我可以看到活动帧的那一刻)的实际开始之间,我会得到一个简短的加载屏幕。理想的情况是,一个视频尽可能无缝地过渡到下一个视频,而不会注意到跳过或加载。我相信当src属性没有完全加载时,这个屏幕只是标签的自然方面,但如果有帮助的话,我会编辑线程添加一个屏幕截图。

我的猜测是,视频[src]属性的更新需要一段时间才能注入,这只相当于我提到的第二个加载屏幕的一部分。

到目前为止我尝试过的东西:

  • 验证我的视频实际上是从存储加载的,而不是试图访问外部URL(它来自存储)
  • 尝试使用循环中相应视频的文件src创建一个标签的动态列表,其中每个标签都是在display:block状态下播放的视频。像这样:
<span *ngFor="let video of playlist; let i = index">
<video *ngIf="video" #videoElement controls (ended)="skipToNextVideoInQueue()"
[style.display]="queuePosition === index ? 'block' : 'none' ">
<source [src]="video.fileSrc" type="video/mp4">
</video>
</span>

上面的尝试引发了这个错误(我只是在这个线程中添加了一些占位符,以保持我的应用程序的一些数据私有):

GET http://192.168.1.45:8100/_capacitor_file_/data/user/0/{{app_name}}/files/{{fileName}}.mp4 net::ERR_FAILED

好吧,坏消息-这不是一个优雅的解决方案。好消息是这是可以做到的。

[注意,这个答案是在我发现最初答案中的一些缺陷后于21年9月10日编辑的]

其主要思想是在第一个视频开始播放时立即开始加载下一个视频。一旦一个视频结束,我们将其显示值设置为none(我们甚至可以通过在节点上执行.remove()方法将其完全删除),然后我们开始自动播放下一个视频。

尽管我想尝试并优化这一点,但我所做的是在ngFor中生成N个标签,基于playlist对象,在我的情况下,该对象来自服务。

模板使用以下内容:

<ng-container *ngFor="let item of playlist; let i = index">
<video #videoElements
id="{{'videoPlayer' + i}}"
(playing)="preloadNextVideo()"
(ended)="skipToNextVideoInQueue()"
preload="auto"
type="video/mp4">
</video>
</ng-container>

该方法需要以下内容才能在控制器中工作:

import { Component, ViewChildren, ElementRef, QueryList } from '@angular/core';
@ViewChildren('videoElements') videoElements: QueryList<ElementRef>;
public videoPlayers = [];
public queuePosition: number;
public playlist: any;
constructor() {}
ngOnInit() {
this.queuePosition = 0;
// the line below is a bit of pseudocode to show we need to get the playlist first
this.myService.getPlaylist.then((result) => {
this.playlist = result;
this.storePlayerRefs();
this.preloadVideo(0);
}
});
// Allow the controller to locate the different children by index
storePlayerRefs() {
setTimeout(() => {
this.videoPlayers = this.videoElements.toArray();
});
}

preloadVideo(position: number) {
setTimeout(() => {
if (position < this.playlist.length) {
const videoToPreload = this.videoPlayers[position];
const sourceTag = document.createElement('source');
// The line below assigns a timestamp to the URL so, in case of refreshes, the source tag will completely reset itself
const fileSrc = this.playlist[position].fileSrc + '?t='+Math.random();
sourceTag.setAttribute('src', fileSrc);
sourceTag.setAttribute('type', 'video/mp4');
videoToPreload.nativeElement.appendChild(sourceTag);
}
});
}

到目前为止,正在进行的是:

  • videoElements queryList开始"监视"ngFor生成的视频标记。在获得播放列表后,由于queryList通常是一个对象的对象,我选择将它们作为数组保存在videoPlayers变量中,因为这样可以更容易地在视频之间切换
  • preloadVideo方法找到播放列表的相应源,创建一个源节点,设置src属性并附加它;自动;选择器,我们不需要调用.load()方法

完成初始设置后,我们进入复杂部分(这是我在开发第一次尝试时感兴趣的地方):

playVideo() {
setTimeout(() => {
const videoElement = 
this.videoPlayers[this.queuePosition].nativeElement;
if (videoElement) {
this.playing = true;
this.hidePreviousPlayer();
videoElement.style.display = 'block';
videoElement.play();
}
});
}

这个方法在第一次调用时,可以在另一个方法成功后手动调用。。。根据您的需要。当视频结束时,ended事件被触发,我们调用这个:

skipToNextVideoInQueue() {
if ((this.queuePosition + 1) < this.playlist.length) {
this.queuePosition ++;
this.playVideo();
}
} else {
// handle whatever you need to do when the playlist is completed
} 

最后但同样重要的是,playVideo()在播放当前元素之前也调用了这个元素:

hidePreviousPlayer() {
if (this.queuePosition > 0) {
setTimeout(() => {
const videoTodelete = this.videoPlayers[this.queuePosition - 1].nativeElement;
videoTodelete.style.display = 'none';
videoTarget.children[0].src = '';
videoTarget.innerHTML = '';
videoTarget.load();
});
}
}

因此,所有这些背后的逻辑如下:

  • queuePosition变量跟踪当时必须播放的视频。它可以具有的最大值与播放列表长度有关,使用该queuePosition作为索引,在任何给定时间,我们都可以知道需要访问什么文件,以及必须选择什么Children
  • 在任何给定的时间预加载i+1视频可以帮助我们消除src属性加载和缓冲播放所需的实际媒体时发生的小"延迟">
  • 视频结束后,我们将queuePosition的值增加一,这样视频的预加载将始终领先当前视频一步。这样,当videoElement.play()方法被激发时,信息已经被预加载并准备就绪
  • 在播放下一个元素之前,hidePreviousPlayer()方法定位i-1视频(也就是队列中的前一个),将其显示设置为无,并在删除之前完全清除源标签。这有助于我们通过保持尽可能少的缓冲区来优化应用程序内存
  • 预加载了下一个视频的信息,视频中没有controls标签,播放列表中有preload="auto",过渡是无缝的

在键入所有这些之后。。。我将是第一个承认这不是最佳的。hidePreviousPlayer方法的重构无疑有助于避免内存问题,但这种方法仍然需要生成N,每个播放列表元素一个。

操作:第二次尝试只使用两个视频标签,并在它们之间切换/预加载,以减少DOM和应用程序必须处理的信息量。

相关内容

  • 没有找到相关文章

最新更新