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 elementosrce 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
srcpropriedade 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
booleanno 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
playingvalor: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 oplayingestado. - Ouça quando o
playingmuda 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.durationlê a duração de um vídeo.videoElement.currentTimelê a posição de reprodução atual de um vídeo.
-
O senhor pode obter o elemento em um stache
on:eventcom 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 …