Neste guia, o senhor aprenderá a criar um player de vídeo personalizado usando o <video>
e o elemento CanJS. O
player de vídeo personalizado será:
- Ter botões personalizados de reprodução e pausa.
- Mostrar a hora atual e a duração do vídeo.
- Tenha um
<input type="range">
que pode ajustar a posição do vídeo.
O jogador final se parece com o senhor:
As seções a seguir estão divididas nas seguintes partes:
- O problema – Uma descrição do que a seção está tentando realizar.
- O que o senhor precisa saber – APIs do navegador ou do CanJS que são úteis para resolver o problema.
- A solução – A solução para o problema.
Instalação
Comece este tutorial bifurcando o seguinte CodePen:
Clique no botão
Edit in CodePen
. O CodePen será aberto em uma nova janela. Clique no botãoFork
.
Este CodePen:
- Cria um
<video>
que carrega um vídeo. Clique com o botão direito do mouse e selecione “Show controls” para ver os controles do vídeo. - Carrega a biblioteca de elementos personalizados do CanJS: Componente.
O problema
Nesta seção, veremos:
-
Criar um
<video-player>
personalizado que recebe um elementosrc
e cria um atributo<video>
elemento
dentro dele mesmo. Devemos ser capazes de criar o vídeo da seguinte forma:<video-player src:raw="http://bit.ly/can-tom-n-jerry"> </video-player>
-
A incorporação
<video>
incorporado deve ter os controles nativos ativados.
Quando concluído, o resultado terá exatamente a mesma aparência do jogador quando o senhor começou. A
única diferença é que estaremos usando um <video-player>
personalizado no elemento HTML
em vez da guia nativa <video>
elemento.
O que o senhor precisa saber
Para configurar um aplicativo (ou widget) CanJS básico, o senhor define um elemento personalizado em JavaScript e
usar o elemento personalizado na seção HTML
.
Para definir um elemento personalizado, estenda o Componente com um tag
que corresponda ao nome do seu elemento personalizado. Por exemplo:
Component.extend({ tag: "video-player" })
Em seguida, o senhor pode usar essa tag em sua página HTML:
<video-player></video-player>
Mas isso não faz nada… ainda. Os componentes adicionam seu próprio HTML por meio de seus visualização
Propriedade:
Component.extend({ tag: "video-player", view: `<h2>I am a player!</h2>` });
Um componente visualização é renderizada com sua ViewModel. Por exemplo, podemos criar um <video>
exibição "http://bit.ly/can-tom-n-jerry"
definindo um src
na propriedade ViewModel
e usá-la no visualização como:
Component.extend({ tag: "video-player", view: ` <video> <source src="https://davidwalsh.name/{{src}}"/> </video> `, ViewModel: { src: {type: "string", default: "http://bit.ly/can-tom-n-jerry"} } });
Mas nós queremos que o <video-player>
tomem uma src
e usá-lo para o
<source>
‘s src
. Por exemplo, se
quiséssemos que o vídeo fosse reproduzido "http://dl3.webmfiles.org/big-buck-bunny_trailer.webm"
em vez de "http://bit.ly/can-tom-n-jerry"
, nós o faríamos:
- Atualização
<video-player>
para passar"http://dl3.webmfiles.org/big-buck-bunny_trailer.webm"
com toChild:raw:<video-player src:raw="http://dl3.webmfiles.org/big-buck-bunny_trailer.webm"/>
- Atualize o ViewModel para definir um
src
propriedade como:Component.extend({ tag: "video-player", view: ` <video> <source src="https://davidwalsh.name/{{src}}"/> {{!👀}} </video> `, ViewModel: { src: "string" } });
Finalmente, para ter um <video>
mostre o elemento nativo nativo, adicione um controls
como:
<video controls>
A solução
Atualize o JS tab para:
import {Component} from "//unpkg.com/can@5/core.mjs"; Component.extend({ //👀 tag: "video-player", //👀 view: ` {{!👀}} <video controls> {{!👀}} <source src="https://davidwalsh.name/{{src}}"/> {{!👀}} </video> {{!👀}} `, //👀 ViewModel: { //👀 src: "string", //👀 } //👀 }); //👀
Atualize o HTML para:
<video-player src:raw="http://bit.ly/can-tom-n-jerry"></video-player> <!--👀-->
Fazer com que o botão de reprodução / pausa mude conforme o vídeo é reproduzido e pausado
O problema
Quando o vídeo é reproduzido ou pausado usando os controles nativos, queremos alterar o conteúdo de um <button>
para dizer “Play” ou “Pausa”.
Quando o vídeo for reproduzido, o botão deverá dizer “Pause” (Pausar).
Quando o vídeo é pausado, o botão deve dizer “Play” (Reproduzir).
Queremos que o botão esteja dentro de um <div>
após o elemento de vídeo, como:
</video> <div> <button>Play</button> </div>
O que o senhor precisa saber
-
Para alterar o conteúdo HTML da página, use {{#if(expression)}} e {{else}} como:
<button>{{#if(playing)}} Pause {{else}} Play {{/if}}</button>
-
O ver responde a valores no ViewModel. Para criar um
boolean
no arquivo ViewModel do:ViewModel: { // ... playing: "boolean", }
-
Os métodos podem ser usados para alterar o ViewModel. A seguir, podemos criar métodos que alteram o
playing
valor:ViewModel: { // ... play() { this.playing = true; }, pause() { this.playing = false; }, }
-
O senhor pode ouvir eventos no DOM com on:event. Por exemplo, o seguinte poderia
ouvir um clique em um<div>
e chamar odoSomething()
:<div on:click="doSomething()">
<video>
Os elementos têm uma variedade de eventosincluindo play e
pausa eventos que são emitidos quando o vídeo é reproduzido ou pausado.
A solução
Atualize o JavaScript tab para:
import {Component} from "//unpkg.com/can@5/core.mjs"; Component.extend({ tag: "video-player", view: ` <video controls on:play="play()" {{!👀}} on:pause="pause()"> {{!👀}} <source src="https://davidwalsh.name/{{src}}"/> </video> <div> {{!👀}} <button> {{!👀}} {{#if(playing)}} Pause {{else}} Play {{/if}} {{!👀}} </button> {{!👀}} </div> {{!👀}} `, ViewModel: { src: "string", playing: "boolean", //👀 play() { //👀 this.playing = true; //👀 }, //👀 pause() { //👀 this.playing = false; //👀 }, //👀 } });
Fazer com que clicar no botão reproduzir/pausar reproduza ou pause o vídeo
O problema
Quando o play/pause <button>
que criamos na seção anterior for clicado, queremos
reproduzir ou pausar o vídeo.
O que o senhor precisa saber
O CanJS prefere gerenciar o estado do seu aplicativo em ViewModel. O <video>
tem um estado, como
se o vídeo for playing
. Quando o play/pause for clicado, queremos atualizar o estado
do botão ViewModel e ter o ViewModel atualizar o estado do player de vídeo como um efeito colateral.
O que isso significa é que, em vez de algo como:
togglePlay() { if ( videoElement.paused ) { videoElement.play() } else { videoElement.pause() } }
Atualizamos o estado como o senhor:
togglePlay() { this.playing = !this.playing; }
E ouça quando o playing
muda e atualize o video
como:
viewModel.listenTo("playing", function(event, isPlaying) { if ( isPlaying ) { videoElement.play() } else { videoElement.pause() } })
Isso significa que o senhor precisa:
- Ouça quando o
<button>
é clicado e chama um método ViewModel que atualiza oplaying
estado. - Ouça quando o
playing
muda e atualiza o estado do<video>
.
O senhor já sabe tudo o que precisa saber para a etapa #1. (Faça com que o botão chame um togglePlay
com o método on:click="togglePlay()"
e faça o togglePlay()
alterne o estado do método playing
).
Por etapa #2, o senhor precisa usar o connectedCallback gancho do ciclo de vida. Esse
dá ao senhor acesso ao elemento do componente e é um bom lugar para fazer efeitos colaterais. Seu uso parece
assim:
ViewModel: { // ... connectedCallback(element) { // perform mischief } }
connectedCallback
é chamado quando o componente element
do componente estiver na página. O senhor pode usar
listenTo para ouvir as alterações no ViewModele
realizam efeitos colaterais. O seguinte escuta quando playing
muda:
ViewModel: { // ... connectedCallback(element) { this.listenTo("playing", function(event, isPlaying) { }) } }
Use querySelector
para obter o <video>
do elemento <video-player>
como:
element.querySelector("video")
<video>
os elementos têm um .play() e .pause() métodos que podem iniciar e parar um vídeo.
A solução
Atualize o JavaScript tab para:
import {Component} from "//unpkg.com/can@5/core.mjs"; Component.extend({ tag: "video-player", view: ` <video controls on:play="play()" on:pause="pause()"> <source src="https://davidwalsh.name/{{src}}"/> </video> <div> <button on:click="togglePlay()"> {{!👀}} {{#if(playing)}} Pause {{else}} Play {{/if}} </button> </div> `, ViewModel: { src: "string", playing: "boolean", play() { this.playing = true; }, pause() { this.playing = false; }, togglePlay() { //👀 this.playing = !this.playing; //👀 }, //👀 connectedCallback(element) { //👀 this.listenTo("playing", function(event, isPlaying) { //👀 if (isPlaying) { //👀 element.querySelector("video").play(); //👀 } else { //👀 element.querySelector("video").pause(); //👀 } //👀 }); //👀 } //👀 } });
Mostrar a hora atual e a duração
O problema
Mostrar a hora atual e a duração do elemento de vídeo. A hora e a duração devem ser
formatados como: mm:SS
. Eles devem ser apresentados em dois intervalos, como:
</button> <span>1:22</span> <span>2:45</span>
O que o senhor precisa saber
-
Os métodos podem ser usados para formatar valores em can-stache. Por exemplo, o senhor pode colocar valores em maiúsculas como este:
<span>{{upper(value)}}</span>
Com um método como:
ViewModel: { // ... upper(value) { return value.toString().toUpperCase(); } }
O seguinte pode ser usado para formatar a hora:
formatTime(time) { if (time === null || time === undefined) { return "--"; } const minutes = Math.floor(time / 60); let seconds = Math.floor(time - minutes * 60); if (seconds < 10) { seconds = "0" + seconds; } return minutes + ":" + seconds; }
-
O tempo é dado como um número. Use o seguinte para criar uma propriedade de número em
o ViewModel:ViewModel: { // ... duration: "number", currentTime: "number" }
-
<video>
os elementos emitem um evento loadmetadata quando souberem a duração
o vídeo é. Eles também emitem um evento timeupdate quando a posição atual de reprodução do vídeo
muda.videoElement.duration
lê a duração de um vídeo.videoElement.currentTime
lê a posição de reprodução atual de um vídeo.
-
O senhor pode obter o elemento em um stache
on:event
com o scope.element como:<video on:timeupdate="updateTimes(scope.element)"/>
A solução
Atualize o JavaScript tab para:
import {Component} from "//unpkg.com/can@5/core.mjs"; Component.extend({ tag: "video-player", view: ` <video controls on:play="play()" on:pause="pause()" on:timeupdate="updateTimes(scope.element)" {{!👀}} on:loadedmetadata="updateTimes(scope.element)"> {{!👀}} <source src="https://davidwalsh.name/{{src}}"/> </video> <div> <button on:click="togglePlay()"> {{#if(playing)}} Pause {{else}} Play {{/if}} </button> <span>{{formatTime(currentTime)}}</span> / {{!👀}} <span>{{formatTime(duration)}} </span> {{!👀}} </div> `, ViewModel: { src: "string", playing: "boolean", duration: "number", //👀 currentTime: "number", //👀 updateTimes(videoElement) { //👀 this.currentTime = videoElement.currentTime || 0; //👀 this.duration = videoElement.duration; //👀 }, //👀 formatTime(time) { //👀 if (time === null || time === undefined) { //👀 return "--"; //👀 } //👀 const minutes = Math.floor(time / 60); //👀 let seconds = Math.floor(time - minutes * 60); //👀 if (seconds < 10) { //👀 seconds = "0" + seconds; //👀 } //👀 return minutes + ":" + seconds; //👀 }, //👀 play() { this.playing = true; }, pause() { this.playing = false; }, togglePlay() { this.playing = !this.playing; }, connectedCallback(element) { this.listenTo("playing", function(event, isPlaying) { if (isPlaying) { element.querySelector("video").play(); } else { element.querySelector("video").pause(); } }); } } });
Fazer com que o intervalo mostre o controle deslizante de posição no momento atual
O problema
Criar um <input type="range"/>
que muda sua posição conforme
a posição de reprodução do vídeo muda.
O <input type="range"/>
deve estar após o elemento <button>
e antes do
currentTime
span like:
</button> <input type="range"/> <span>{{formatTime(currentTime)}}</span> /
O que o senhor precisa saber
-
A entrada do intervalo pode ter um valor inicial, um valor máximo e um tamanho de etapa
especificados como:<input type="range" value="0" max="1" step="any"/>
-
O intervalo terá valores de 0 a 1. Precisaremos traduzir o currentTime para
um número entre 0 e 1. Podemos fazer isso com um propriedade getter computada como:ViewModel: { // ... get percentComplete() { return this.currentTime / this.duration; }, }
-
Use key:from para atualizar um valor de um ViewModel propriedade como:
<input value:from="percentComplete"/>
A solução
Atualize o JavaScript tab para:
import {Component} from "//unpkg.com/can@5/core.mjs"; Component.extend({ tag: "video-player", view: ` <video controls on:play="play()" on:pause="pause()" on:timeupdate="updateTimes(scope.element)" on:loadedmetadata="updateTimes(scope.element)"> <source src="https://davidwalsh.name/{{src}}"/> </video> <div> <button on:click="togglePlay()"> {{#if(playing)}} Pause {{else}} Play {{/if}} </button> <input type="range" value="0" max="1" step="any" {{!👀}} value:from="percentComplete"/> {{!👀}} <span>{{formatTime(currentTime)}}</span> / <span>{{formatTime(duration)}} </span> </div> `, ViewModel: { src: "string", playing: "boolean", duration: "number", currentTime: "number", get percentComplete() { //👀 return this.currentTime / this.duration; //👀 }, //👀 updateTimes(videoElement) { this.currentTime = videoElement.currentTime || 0; this.duration = videoElement.duration; }, formatTime(time) { if (time === null || time === undefined) { return "--"; } const minutes = Math.floor(time / 60); let seconds = Math.floor(time - minutes * 60); if (seconds < 10) { seconds = "0" + seconds; } return minutes + ":" + seconds; }, play() { this.playing = true; }, pause() { this.playing = false; }, togglePlay() { this.playing = !this.playing; }, connectedCallback(element) { this.listenTo("playing", function(event, isPlaying) { if (isPlaying) { element.querySelector("video").play(); } else { element.querySelector("video").pause(); } }); } } });
Fazer com que o deslizamento do intervalo atualize a hora atual
O problema
Nesta seção, veremos:
- Remova os controles nativos do player de vídeo. Não precisamos mais deles!
- Faça com que, quando um usuário mover o controle deslizante de intervalo, a posição do vídeo seja atualizada.
O que o senhor precisa saber
Semelhante a quando nós fizemos o botão reproduzir/pausar reproduzir ou pausar o vídeo, vamos querer atualizar o
currentTime
e, em seguida, ouvir quando o currentTime
muda e atualiza a propriedade <video>
do elemento currentTime
como um efeito colateral.
Desta vez, precisamos traduzir os valores dos controles deslizantes entre 0 e 1 para currentTime
valores. Podemos fazer isso criando um percentComplete
setter que atualiza currentTime
como:
ViewModel: { // ... get percentComplete() { return this.currentTime / this.duration; }, set percentComplete(newVal) { this.currentTime = newVal * this.duration; }, // ... }
Use key:bind para vincular de forma bidirecional um valor a um ViewModel propriedade:
<input value:bind="someViewModelProperty"/>
A solução
Atualize o JavaScript tab para:
import {Component} from "//unpkg.com/can@5/core.mjs"; Component.extend({ tag: "video-player", view: ` <video {{!👀}} on:play="play()" on:pause="pause()" on:timeupdate="updateTimes(scope.element)" on:loadedmetadata="updateTimes(scope.element)"> <source src="https://davidwalsh.name/{{src}}"/> </video> <div> <button on:click="togglePlay()"> {{#if(playing)}} Pause {{else}} Play {{/if}} </button> <input type="range" value="0" max="1" step="any" value:bind="percentComplete"/> {{!👀}} <span>{{formatTime(currentTime)}}</span> / <span>{{formatTime(duration)}} </span> </div> `, ViewModel: { src: "string", playing: "boolean", duration: "number", currentTime: "number", get percentComplete() { return this.currentTime / this.duration; }, set percentComplete(newVal) { //👀 this.currentTime = newVal * this.duration; //👀 }, //👀 updateTimes(videoElement) { this.currentTime = videoElement.currentTime || 0; this.duration = videoElement.duration; }, formatTime(time) { if (time === null || time === undefined) { return "--"; } const minutes = Math.floor(time / 60); let seconds = Math.floor(time - minutes * 60); if (seconds < 10) { seconds = "0" + seconds; } return minutes + ":" + seconds; }, play() { this.playing = true; }, pause() { this.playing = false; }, togglePlay() { this.playing = !this.playing; }, connectedCallback(element) { this.listenTo("playing", function(event, isPlaying) { if (isPlaying) { element.querySelector("video").play(); } else { element.querySelector("video").pause(); } }); this.listenTo("currentTime", function(event, currentTime) { //👀 const videoElement = element.querySelector("video"); //👀 if (currentTime !== videoElement.currentTime) { //👀 videoElement.currentTime = currentTime; //👀 } //👀 }); //👀 } } });
Resultado
Quando terminar, o senhor deverá ver algo parecido com o seguinte JS Bin:
Sobre Justin Meyer
Justin Meyer é colaborador de CanJS (uma estrutura de aplicativo da Web), StealJS (um carregador de módulos) e DoneJS (uma pia de cozinha). Ele também é um dos fundadores do Bitovi (uma consultoria na Web). Como escritor de artigos autobiográficos, ele sabe usar seus dois filhos, seu amor por livros de história e hobbies como correr para parecer mais humano. Mas será que ele é realmente …