Conteúdo


Crie um player com playlist personalizada do YouTube

Estenda o reprodutor integrado básico para incluir os mesmos recursos que o reprodutor nativo YouTube.com

Lançado em 2005, o YouTube evoluiu para o website de compartilhamento de vídeo dominante. As listas de execução são um dos recursos mais usados do YouTube. É possível criar listas de execução próprias e vídeos transferidos por upload de outros usuários e compartilhar suas listas de execução no YouTube. Também é possível compartilhar suas listas de execução integrando-as em seu website, blog ou página de mídia social. Entretanto, o reprodutor de lista de execução integrado não possui a funcionalidade integral do reprodutor nativo no YouTube.com.

A Figura 1 mostra o reprodutor de lista de execução nativo youtube.com.

Figura 1. Reprodutor de lista de execução nativo
Screenshot shows the native YouTube playlist player
Screenshot shows the native YouTube playlist player

A Figura 2 mostra o reprodutor integrado padrão.

Figura 2. Reprodutor de lista de execução integrado
Screenshot shows the default embedded YouTube playlist player
Screenshot shows the default embedded YouTube playlist player

No reprodutor integrado:

  • A lista de vídeos incluídos é oculta por padrão e apenas aparece como uma sobreposição, não ao lado do reprodutor, como no YouTube.com.
  • A capacidade de escolher aleatoriamente a lista de execução foi removida.
  • As notas incluídas na lista de execução por seu autor são removidas.

Encontrei estas limitações quando decidi integrar uma lista de execução com alguns dos melhores gols pontuados nos jogos de qualificação para a Copa do Mundo FIFA 2014 no meu blog. Com ajuda da API do YouTube, jQuery, o mecanismo de modelagem JsRender e a estrutura front-end de autoinicialização, eu estendi e melhorei o reprodutor integrado padrão para criar uma versão equivalente para o reprodutor nativo. Eu o acompanharei no mesmo processo neste artigo. Você criará uma lista de execução no YouTube, incluirá notas com os horários dos destaques (como gols), em seguida, usará as mesmas ferramentas para criar um reprodutor integrado que restaura a funcionalidade ausente e melhora a experiência do usuário.

O código do projeto completo é armazenado no DevOps Services. Eu desenvolvi o aplicativo para IBM Cloud™ para que possa vê-lo em operação. É possível usar qualquer site de host, incluindo IBM Cloud, para implementar seu código.

Execute o reprodutor Obtenha o código

Observação: para bifurcar o código para o projeto deste artigo, clique no botão EDIT CODE no canto superior direito (insira suas credenciais do DevOps Services se ainda não tiver efetuado login) e clique no botão FORK no menu para criar um novo projeto.

A primeira etapa é obter uma chave para acessar a API do YouTube.

Obtenha uma chave de API do YouTube

Para acessar a API para qualquer serviço do Google, incluindo YouTube, você deve primeiro registrar um projeto no Google Developers Console e criar uma chave de API. O acesso a várias APIs é livre até um certo número de solicitações por dia, que varia de serviço a serviço e está disponível a qualquer um com uma conta do Google.

Conecte-se ao Google Developers Console com suas credenciais do Google e clique em Create Project. Por padrão, as caixas de texto de nome do projeto e de ID do projeto contêm valores aleatórios. Insira seu nome do projeto e ID e clique em Create.

Figura 3. Criando um novo projeto de API do Google
Screenshot of the Google Developer Console's New Project dialog box
Screenshot of the Google Developer Console's New Project dialog box

No painel do projeto, clique em APIs & auth para abrir a lista de APIs disponíveis. Role para baixo até YouTube Data API v3 e clique no botão associado titulado Off para permitir o acesso para seu projeto. Selecione APIs & Auth > Credentials e Create New Key em Public API Access. Escolha Browser key.

Na caixa de diálogo Criar uma chave do navegador e configurar o diálogo de referências permitidas, é possível restringir o acesso à chave de API para solicitações de certos domínios, como seu próprio site ou IBM Cloud. A menos que restrinja o acesso, sua chave de API estará visível a todos em sua origem HTML do aplicativo. Durante o desenvolvimento, este não é um problema, portanto, deixe o diálogo em branco e clique em Create. Mas quando o projeto for implementado, retorne para esta etapa e restrinja o acesso a solicitações de seu domínio do aplicativo para que terceiros não possam usar a chave em outros aplicativos.

Se você clonou o projeto do DevOps Services, é possível inserir a chave agora onde indicado no arquivo index.html para permitir que o código seja executado com sucesso.

Configure a biblioteca do cliente Google JavaScript para acessar a API do YouTube

O Google fornece bibliotecas do cliente para vários idiomas, incluindo JavaScript. Importe o cliente JavaScript em seu documento HTML incluindo esta tag HTML na tag <body> do documento (não na tag <head> ):

<script src="https://apis.google.com/js/client.js?onload=function" ></script>

A melhor prática recomendada é colocar esta tag <script> no fim da tag <body> .

Após a biblioteca do cliente ser carregada, o objeto gapi (API do Google) fica disponível no escopo da janela em sua página. O parâmetro onload na tag <script> que acabou de incluir refere-se a uma função de retorno de chamada feita imediatamente após a biblioteca ser carregada. A definição dessa função deve preceder a tag <script> que carrega o cliente. A função chama o método gapi.client.load(api name, versão, função de retorno da chamada ) para carregar as APIs que o cliente usará. Neste caso, você carrega a API do YouTube com gapi.client.load('youtube', 'v3', onYouTubeApiLoad). onYouTubeApiLoad é uma função função de uma linha que chama o método setAPIKey .Em setAPIKey, você configura a chave para o valor de sua chave de navegador do Google.

O cliente fornece os métodos para acessar as várias chamadas API para serviços do Google. Para obter a lista de itens em uma lista de execução, você analisará a resposta do método playlistItems.list na API do YouTube em uma função assíncrona e armazenará os atributos relevantes em uma instância de um objeto JavaScript chamado YouTubePlayList. Você desenvolverá o objeto YouTubePlaylist no decorrer de todo este artigo. O construtor do objeto é definido na função JavaScript mostrada abaixo.

Lista 1. YouTubePlaylist.js
function YouTubePlaylist(id, entries) {
   this.id = id;
   this.entries = entries;
   this.currently_playing = 0;
   this.randomizer = false;
}
  • id é o ID da lista de execução.
  • entries é uma matriz JSON dos vídeos na lista de execução.
  • currently_playing é o índice do vídeo em execução atualmente na matriz entries .
  • randomizer indica se a reprodução é escolhida aleatoriamente.

Crie um objeto JSON com parâmetros de resposta

Agora, crie um objeto JSON com os parâmetros necessários na resposta. Só é necessário um pequeno subconjunto da lista completa de parâmetros disponíveis.

O parâmetro part é uma lista de valores separados por vírgula (CSV) dos atributos a serem retornados pela chamada. Use os atributos contentDetails e snippet . O atributo snippet contém informações básicas sobre cada vídeo. contentDetails contém o ID do vídeo e quaisquer notas incluídas pelo autor de lista de execução. contentDetails será importante posteriormente quando você identificar os destaques no vídeo. O parâmetro playListId é o ID da lista de execução que você usará. Para os fins deste artigo, eu configurei uma lista de execução com destaques cujo ID é PLLzJfby7cTLTbusOgXca-yIpVOImC1mWe. Na lista de execução, observe que os horários dos objetivos são incluídos como notas. O objeto JSON requestOptions agora se parece com:

var requestOptions = {
   playlistId: playlist_id,
   part: 'contentDetails,snippet'
};

Chamar o método gapi.client.youtube.playlistItems.list() com este objeto JSON como um parâmetro retorna um objeto com dois métodos: executa e subscribe. Chame o método executa com a função assíncrona, com a resposta como um parâmetro.

request.execute(function(response) {});

A matriz items na resposta contém a lista de vídeos na lista de execução. Você usará o método jQuery each() para iterar por meio dos itens. Você armazenará o ID do vídeo, a miniatura de tamanho médio para o vídeo, o título e a nota em um objeto JSON, em seguida, inclua isto em uma matriz.

Lista 2. Incluindo o objeto JSON na matriz entries .
var entries = [];
$.each( response.items, function( key, val ) {

   var entry = {};
   entry.video_id = val.snippet.resourceId.videoId;
   entry.image_src = val.snippet.thumbnails.medium.url;
   entry.title = val.snippet.title;
   entry.note = val.contentDetails.note;
   entries.push(entry);

});

Crie a visualização YouTubePlayList object

Crie o novo objeto YouTubePlaylist chamando o construtor (consulte a Listagem 1) com o ID da lista de execução e a matriz entries e armazene o objeto no escopo da janela como uma nova variável chamada com o ID da lista de execução.

window[playlist_id] = new YouTubePlaylist(playlistId, entries);

Agora, é possível acessar o objeto YouTubePlaylist usando window[playlist_id]. Posteriormente, você usará window[playlist_id] para chamar a funcionalidade adicional do objeto YouTubePlaylist .

Use um modelo para formatar o reprodutor com JsRender

O esboço de seu reprodutor será como a Figura 4, com a miniatura, título e lista de nota à direita composta de entradas na lista de execução.

Figura 4. Esboço do novo reprodutor
Illustration shows the structure of the new playlist player
Illustration shows the structure of the new playlist player

Por padrão, o conjunto de caracteres HTML não inclui ícones semelhantes aos próximos ícones, aos anteriores e aos aleatórios no reprodutor nativo do YouTube. Seu aplicativo usará ícones fornecidos pela estrutura front-end de autoinicialização. Para importar a folha de estilo de autoinicialização, inclua o fragmento de código a seguir na tag <head> .

<link rel="stylesheet" 
      type="text/css" 
      href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">

Você renderizará o objeto YouTubePlaylist usando o mecanismo de modelagem JsRender. Você define um modelo JsRender em uma tag <script> com o atributo Unknown configurado como text/x-jsrender. Você gera o HTML chamando o método render() de um modelo x-jsrender com o objeto a ser renderizado. Você renderiza variáveis no modelo, usando a notação de colchetes duplos {{:}}. Por exemplo, {{:id}} renderiza o atributo id do objeto passado para o modelo.

O modelo mostrado na Listagem 3 integra um reprodutor do YouTube em uma página da web.

Lista 3. Modelo para integrar um reprodutor do YouTube
<object width="640" 
        height="360" 
data="http://www.youtube.com/v/video id?version=3&amp;enablejsapi=1&amp;playerapiid=id"
        id="player id" 
        type="application/x-shockwave-flash">
   <param value="always" name="allowScriptAccess">
   <param value="true" name="allowFullScreen">
</object>

Na Listagem 3, video id é o ID do vídeo a ser reproduzido e player id é o ID do próprio objeto do reprodutor. Na inicialização, você sugerirá o primeiro vídeo na matriz entries, portanto, insira {{:entries[0].video_id}} como o ID do vídeo no modelo de reprodutor integrado. Para o ID do reprodutor, use o ID de lista de execução, a saber {{:id}}.

Na inicialização de um objeto do reprodutor do YouTube, o reprodutor automaticamente chamará a função onYouTubePlayerReady() com o ID do reprodutor como um parâmetro para customizar seu comportamento quando seu estado for alterado. Os estados de um reprodutor são não iniciado, terminado, em execução, pausado, em buffer e vídeo sugerido; eles são enumerados como -1, 0, 1, 2, 3 e 4. Na versão atual da API, a função de retorno de chamada não poderá ser customizada. Por ora, você definirá uma função no escopo da janela para carregar o próximo vídeo na lista de execução (você definirá esta função no objeto YouTubePlaylist posteriormente) e incluirá como um listener de eventos no reprodutor.

Lista 4. Função para carregar o próximo vídeo na lista de execução
function onYouTubePlayerReady(playerApiId) {
   var player = document.getElementById(playerApiId);
   window["onStateChange" + playerApiId] = function(state) {   
      switch(state) {
         case 0:                         
var video_player = document.getElementById(player_id);
video_player.loadVideoById(window[player_id].getNextVideo(), 0, "large"); 
break; } }; player.addEventListener("onStateChange", "onStateChange" + playerApiId); }

Para iterar através da matriz de vídeos, você usará a tag {{for}} do JsRender. Você criará cada entrada da lista de execução à direita na Figura 4 com o modelo na Listagem 5.

Lista 5. Modelo para criar cada entrada da lista de execução na lista
{{for entries}}
<div class="playListEntry {{if #index == 0}}nowPlaying{{/if}}" id="{{:video_id}}">
   <div class="playListEntryThumbnail">
      <img src="{{:image_src}}"/>
   </div>
   <div class="playListEntryDescription">
      <div class="playListEntryTitle">{{:title}}</div>
      <div class="playListEntryNote">{{:note}}</div>
   </div>
</div>
{{/for}}

Na Listagem 5, entries é o atributo entries no objeto YouTubePlaylist . O índice do objeto na matriz é armazenado na variável #index . Como a primeira entrada na lista é o vídeo carregado no reprodutor na criação, você aplicará a classe CSS nowPlaying na primeira classe playListEntry usando a tag {{if}} para identificá-la no loop for .

Os controles na parte inferior do reprodutor são criados aplicando a classe glyphicon de autoinicialização a um span, usando as classes glyphicon-backward, glyphicon-forward e glyphicon-random para os ícones.

<div class="playListControls">
   <span class="playListControl disabled glyphicon glyphicon-backward"/>
   <span class="playListControl glyphicon glyphicon-forward"/>
   <span class="playListControl glyphicon glyphicon-random"/>
</div>

Observe que inicialmente, o ícone "anterior" está desativado, porque quando o reprodutor carrega pela primeira vez, ele padroniza a primeira entrada na lista de execução, portanto, não existe vídeo anterior a ser reproduzido.

Você incluirá o HTML renderizado para um div vazio na página com o ID playlist com este fragmento de código (lembrando que window[player_id] se refere ao objeto criado na seção Criar YouTubePlaylist".

$('#' + player_id).html($('#playListPlayerTemplate').render(window[player_id]));

Para tornar este código reutilizável, mova-o para uma função com a assinatura addPlaylistToElement(playlist_id, element_id) Então ele pode ser chamado com addPlaylistToElement('PLLzJfby7cTLTbusOgXca-yIpVOImC1mWe', 'playlist').

Incluir controles

Volte para o objeto YouTubePlaylist e comece a incluir a funcionalidade. Posteriormente, você usará esta versão aprimorada do objeto para concluir o reprodutor na página da web.

Inclua seis funções no objeto:previous(), next(), getCurrentlyPlaying(), setCurrentlyPlaying(), randomize() e isRandomized(). As funções anterior e próxima movem para os vídeos relevantes na lista de execução e retornam true se a ação for bem-sucedida ou false se não for (isto é, se o usuário clicar em "anterior" na primeira entrada na lista de execução ou "próxima" na última entrada). getCurrentlyPlaying() retorna o ID do vídeo em execução atualmente na lista de execução. randomize() configura ou desconfigura o atributo aleatório no objeto e isRandomizer() retorna o valor do atributo aleatório.

A Listagem 6 mostra a função Next() .

Lista 6. O tempo de execução do perfil Next() function
next: function() {
   var retVal = false;
   if(this.randomizer) {
      retVal = true;
      this.currently_playing = Math.floor((Math.random() * this.entries.length));
   }
   else if(this.currently_playing <= this.entries.length) {
      retVal = true;
      this.currently_playing++;
   } 
   return retVal;

Na função Next() , primeiro verifique se o atributo aleatório está configurado e, se estiver, configure o índice currently_playing como um valor aleatório na matriz entries. Se o atributo aleatório não estiver configurado e o índice currently_playing for menor que o número de vídeos na matriz (isto é, você não passou o último vídeo na lista de execução), incremente o valor do índice em um para mover para o próximo vídeo e retorne true para indicar que a operação foi bem-sucedida. Se não foi bem-sucedida, retorne false,.

A Listagem 7 mostra a função previous(). Se o índice currently_playing for maior que zero (isto é, o usuário estiver assistindo a qualquer vídeo exceto o primeiro vídeo na lista de execução), diminua o índice em um e retorne true para indicar que a operação foi bem-sucedida; caso contrário, retorne false,.

Lista 7. O tempo de execução do perfil previous() function
previous: function() {
   var retVal = false;
   if(this.currently_playing > 0) {
      retVal = true;
      this.currently_playing--;
   } 
   return retVal;
}

Na função getCurrentlyPlaying() , retorne o ID do vídeo do índice em execução atualmente na matriz de entradas.

getCurrentlyPlaying: function() {
   return this.entries[this.currently_playing].video_id;
}

A Listagem 8 mostra a função setCurrentlyPlaying() . Dado um video_id da lista de execução atual, configure currently_playing como o índice do elemento na matriz de entradas com esse valor.

Lista 8. O tempo de execução do perfil setCurrentlyPlaying() function
setCurrentlyPlaying: function(video_id) {
   for(var index = 0; index < this.entries.length; index++) {
      if (this.entries[index].video_id === video_id) {
         this.currently_playing = index;
         break;
      }
   }
}

Na função randomize() , inverta o valor do atributo randomizer— de true para false e vice-versa — e retorne o novo valor.

randomize: function() {
   this.randomizer = !(this.randomizer);
   return this.randomizer;
}

A função isRandomized() retorna o valor do atributo randomizer da lista de execução — isto é, se a lista de execução está na reprodução aleatória:

isRandomized: function() {
   return this.randomizer;
}

Usar a funcionalidade incluída

Agora, inclua funções para usar a funcionalidade incluída do objeto JavaScript.

Primeiro, inclua uma função auxiliar para organizar os controles de uma lista de execução. Se o reprodutor estiver na reprodução aleatória:

  • O ícone "aleatório" é destacado.
  • O ícone "anterior" está sempre desativado, porque você não está gravando o vídeo reproduzido anteriormente em lugar algum.
  • O ícone "próximo" está sempre ativado, porque a lista de execução nunca pode reproduzir o "último" vídeo aleatoriamente. (Pense nisso: Quando seu reprodutor MP3 está no modo aleatório, ele alguma vez para de reproduzir?)

Se a lista de execução não estiver na reprodução aleatória:

  • O ícone "anterior" é desativado apenas se a primeira entrada em uma lista de execução estiver em execução.
  • O ícone "próximo" é desativado apenas se a última entrada na lista de execução estiver em execução.
  • O ícone "aleatório" é desativado até que seja clicado novamente.

A Listagem 9 mostra a função auxiliar.

Lista 9. Função auxiliar para organizar os controles da lista de execução
function arrangePlayerControls(player_id) {
   var playListPlayer = $('#' + player_id + 'playListPlayer');
   if(window[player_id].isRandomized()) {
      $('#' + player_id + 'Backward').addClass('disabled');
      $('#' + player_id + 'Forward').removeClass('disabled');
      $('#' + player_id + 'Random').addClass('randomizeActive');
   }
   else {
      $('#' + player_id + 'Random').removeClass('randomizeActive');
      var playListEntries = $('#' + player_id + 'playListEntries');
      if(playListEntries.children(":first").hasClass('nowPlaying')) {
         $('#' + player_id + 'Backward').addClass('disabled');
      }
      else {
         $('#' + player_id + 'Backward').removeClass('disabled');
      }
      if(playListEntries.children(":last").hasClass('nowPlaying')) {
         $('#' + player_id + 'Forward').addClass('disabled');
      }
      else {
         $('#' + player_id + 'Forward').removeClass('disabled');
      }
   }
}

Em seguida, inclua uma função para carregar um vídeo no reprodutor para um determinado ID de lista de execução e o índice de horário para começar. Lembre-se de remover a classe nowPlaying de div para o vídeo em execução e inclua-a no div para o novo vídeo. Em seguida, chame a função auxiliar na Listagem 9 para organizar os ícones da lista de execução. A Listagem 10 mostra a função de carregamento de vídeo.

Lista 10. A função para carregamento de um vídeo no reprodutor
function loadVideoForPlayer(currently_playing_video_id, player_id, time) {
   time = time || 0;
   var video_id = window[player_id].getCurrentlyPlaying();
   $('#' + currently_playing_video_id).removeClass('nowPlaying')
   $('#' + video_id).addClass('nowPlaying');
   $('#' + player_id + 'playListEntries').scrollTop($('#' + video_id).index() * 80);
   document.getElementById(player_id).loadVideoById(video_id, time, "large");
   arrangePlayerControls(player_id);
}

Finalmente, inclua uma função para carregar o próximo vídeo em uma determinada lista de execução, mas apenas se outro vídeo estiver na lista de execução (isto é, você não está na reprodução aleatória e o vídeo atual não é o último vídeo).

Lista 11. Função para carregar o vídeo se um próximo vídeo existir
function loadNextVideo(player_id) {
   var currently_playing_video_id = window[player_id].getCurrentlyPlaying();
   if(window[player_id].next()) {
      loadVideoForPlayer(currently_playing_video_id, player_id);
   }
}

Esta função é semelhante à função anônima declarada em onYouTubePlayerReady() (consulte a Listagem 4), portanto, refatore o bloco case 0 em onYouTubePlayerReady() para chamar loadNextVideo() .

Você pode ter notado que incluí o horário para os objetivos em cada vídeo na lista de execução. Com estas novas funções, é possível usar os horários dos objetivos como pontos de acesso de link para pular direto para o objetivo no vídeo, em vez de precisar assistir tudo de novo. No loop $.each() em addPlaylistToElement(), armazene o valor da nota para cada objeto playlistItem em uma variável local, em seguida, use a função JavaScript match() para retornar uma matriz de horários da nota, usando a expressão regular /[0-9]*:[0-5][0-9]/g para encontrar cada horário. É possível então fazer um loop por esta matriz e substituir o valor de cada horário na variável por um link para chamar a função cueThisVideo() com o ID do reprodutor, o vídeo a ser reproduzido e o índice de horário para começar. Lembre-se de que a chamada API do YouTube loadVideoById() pega o horário em segundos, então divida o horário em uma matriz, usando dois pontos na sequência como um delimitador. Multiplique o valor no primeiro índice (os minutos) por 60 para convertê-lo em segundos e inclua-o nos segundos no segundo índice para obter o número total de segundos. Por exemplo, 1:30 torna-se a matriz [1, 30] (1 * 60) + 30 = 90 segundos. Finalmente, substitua o horário na nota pelo novo link. Quando cada horário na nota tiver sido processado, armazene a sequência concluída como a nota para a entrada na matriz entries, conforme mostrado na Listagem 12.

Lista 12. Substituindo os horários na nota por um link
var note = val.contentDetails.note;
var times = note.match(/[0-9]*:[0-5][0-9]/g);
times.forEach(function(value, index, array) {
   var time = value.split(":");
   var seconds = parseInt(time[0]) * 60;
   seconds += parseInt(time[1]);
   note = note.replace(value, 
     "<span class='timeLink' onclick='cueThisVideo(\"" 
     + player_id + "\", \"" 
     + video_id + "\", " 
     + seconds + ");'>" 
     + value + "</span>");
});
entry.note = note;

Tudo que permanece é para revisitar o modelo e incluir chamadas em as novas funções. Você deseja que o usuário possa enfileirar um vídeo clicando no título ou miniatura, enfileire os vídeos anteriores ou próximos na lista de execução e deixe-a no aleatório com os botões relevantes no painel de controle. A Listagem 13 mostra as mudanças concluídas no modelo para usar a funcionalidade incluída.

Lista 13. Código de modelo refatorado
<div onclick="cueThisVideo('{{:~player_id}}', '{{:video_id}}');" 
     class="playListEntryThumbnail">
          <img src="{{:image_src}}"/>
</div>
<div onclick="cueThisVideo('{{:~player_id}}', '{{:video_id}}');" 
     class="playListEntryTitle">
     {{:title}}
</div>

<span id="{{:id}}Backward" 
     class="playListControl disabled glyphicon glyphicon-backward" 
     onclick="if(!$(this).hasClass('disabled'))	{   loadPreviousVideo('{{:id}}')   }">
</span>
<span id="{{:id}}Forward" 
     class="playListControl glyphicon glyphicon-forward" 
     onclick="if(!$(this).hasClass('disabled')) {   loadNextVideo('{{:id}}')   }">
</span>
<span id="{{:id}}Random" 
     class="playListControl glyphicon glyphicon-random" 
     onclick="window['{{:id}}'].randomize();arrangePlayerControls('{{:id}}');">
</span>

Agora, seu reprodutor customizado está pronto para arrasar!

Conclusão

Este artigo demonstrou como usar a API do YouTube e um pouco de JavaScript e estilo simples para renderizar uma lista de execução integrada do YouTube com a funcionalidade equivalente à lista de execução nativa no YouTube.com. Consulte o arquivo LEIA-ME na minha página DevOps Services do projeto para algumas sugestões para aprimorar o reprodutor ainda mais. Sinta-se à vontade para bifurcar o código do projeto para implementar qualquer ou todas essas sugestões.


Recursos para download


Temas relacionados

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Cloud computing
ArticleID=1001297
ArticleTitle=Crie um player com playlist personalizada do YouTube
publish-date=03252015