Skip to content

Instantly share code, notes, and snippets.

@sfpgmr
Last active October 23, 2016 09:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sfpgmr/a8cf77b5ac3e7f3393c1 to your computer and use it in GitHub Desktop.
Save sfpgmr/a8cf77b5ac3e7f3393c1 to your computer and use it in GitHub Desktop.
再生リストのサムネイルとプレビュー

このサンプルについて

Youtube Data APIとYoutube Player APIを使用した勉強を兼ねたサンプルコードです。

まずプレイリスト一覧を表示します。クリックするとプレイリストの中身をサムネイル表示します。 サムネイルの上にマウスカーソルを重ねるとプレビュー再生します。

ChannelIDを変更すると他のチャンネルのプレイリストを表示します。

※このサンプルは「Open in a new window」で確認してください。

http://bl.ocks.org/sfpgmr/raw/a8cf77b5ac3e7f3393c1/

使用したライブラリ

  • d3.js
  • q.js
  • Bootstrap
  • jquery

##ライセンス

  • MITライセンス

##リンク

<!DOCTYPE html>
<html vocab="http://schema.org">
<head>
<title>再生リストのサムネイルとプレビュー</title>
<meta charset="utf-8" />
<meta name="description" content="クリックすると再生リストの中身をサムネイル表示します。サムネイルの上にマウスカーソルを重ねるとプレビュー再生できます。ChannelIDを変更すると他のチャンネルのプレイリストを表示します。" />
<meta name="keywords" content="Youtube,d3.js,Q.js,jquery" />
<meta name="author" content="sfpgmr" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
<!-- Latest compiled and minified JavaScript -->
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/q.js/1.0.1/q.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.2/d3.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script type="text/javascript" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
<script type="text/javascript" src="//www.sfpgmr.net/scripts/youtube_apikey.js"></script>
<style>
html {
position: relative;
height: 100%;
}
body {
/* Margin bottom by footer height */
margin-top: 50px;
margin-bottom: 100px;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
/* Set the fixed height of the footer here */
height: 60px;
background-color: #f5f5f5;
}
div#playerObj {
display: none;
position: absolute;
}
.thumbnail-title {
color: white;
position: absolute;
text-shadow: 1px 1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, -1px -1px 0 #000;
font-size: 14px;
}
</style>
<script type="text/javascript">
if (!location.href.match(/localhost/)) {
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-15457703-11']);
_gaq.push(['_trackPageview']);
(function () {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
}
</script>
</head>
<body>
<nav class="navbar navbar-default navbar-fixed-top " role="navigation">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<div><a class="navbar-brand" href="#" property="headLine" id="headLine" style="margin-top:auto;margin-bottom:auto;">Youtube API Test</a></div>
</div>
<div class="collapse navbar-collapse" id="navbar-collapse">
<p class="navbar-text">ChannelID</p>
<form class="navbar-form navbar-left" role="search" id="channelIDForm">
<div class="form-group">
<input id="channelID" style="width:200px" type="text" class="form-control" placeholder="ChannelIDを入力">
</div>
</form>
<p class="navbar-text"><a id="displayPlayLists" href="#" class="navbar-link">プレイリスト一覧</a></p>
<p class="navbar-text navbar-right"><a id="homeLink" href="/" class="navbar-link"><span class="glyphicon glyphicon-home"></span>ホーム</a></p>
</div>
</div>
</nav>
<article id="article" typeof="Article">
<header class="container"><h2>再生リストのサムネイルとプレビュー</h2>
<p>クリックすると再生リストの中身をサムネイル表示します。サムネイルの上にマウスカーソルを重ねるとプレビュー再生できます。ChannelIDを変更すると他のチャンネルのプレイリストを表示します。</p>
<h4 id="playListTitle"></h4>
</header>
<div class="container">
<section id="articles" class="row"></section>
<aside style="margin-left:auto;margin-right:auto;display:block;">
<style>
.blocks {
width: 320px;
height: 50px;
}
@media(min-width: 500px) {
.blocks {
width: 468px;
height: 60px;
}
}
@media(min-width: 800px) {
.blocks {
width: 728px;
height: 90px;
}
}
</style>
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<!-- blocks -->
<ins class="adsbygoogle blocks"
style="display:inline-block;text-align:center;"
data-ad-client="ca-pub-4402137407996189"
data-ad-slot="3288576128"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
</aside>
</div>
</article>
<footer id="footer" class="navbar navbar-default navbar-fixed-bottom">
<div class="container">
<div class="row">
<div class="col-xs-3" property="creator" id="creator">
<div typeof="person" id="creatorPerson">
<span property="name" id="creatorName">By <a href="http://www.sfpgmr.net/">S.F.</a></span>
</div>
</div>
<div class="col-xs-3 text-center"></div>
<div class="col-xs-6 text-right"><small><time id="date" property="dc:date" datetime="2015-01-12T19:19:52.381Z">2015-01-12T19:19:52.381Z</time></small></div>
</div>
</div>
</footer>
<div id="playerObj">
<div id="player">
</div>
</div>
<script type="text/javascript">
/// <reference path="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.2/d3.js" />
"use strict";
var player;
var playerWidth = 640;
var playerHeight = 360;
d3.select('head').append('script').attr('src', 'http://www.youtube.com/iframe_api');
if (window.location.href.match(/bl\.ocks\.org/i)) {
d3.select('#homeLink').attr('href', 'http://bl.ocks.org/sfpgmr/');
}
d3.select('#channelID').node().value = 'UCgwM0kBBsDRMZDhhWuTNR-g';
d3.select('#displayPlayLists').on('click', function () {
d3.event.preventDefault();
doPlayLists(d3.select('#channelID').node().value);
});
d3.select('#channelIDForm').on('submit', function () {
d3.event.preventDefault();
doPlayLists(d3.select('#channelID').node().value);
});
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
width: playerWidth,
height: playerHeight,
playerVars: {
'fs': 1,
'hd': 1
},
events: {
'onReady': onPlayerReady
},
enablejsapi: '1'
});
function onPlayerReady(event) {
d3.select('#player').on('mouseleave', stopVideo);
d3.select(window).on('mouseleave', stopVideo);
}
}
function stopVideo() {
if (player) {
player.stopVideo();
var p = d3.select('#playerObj');
p.style('display', 'none');
d3.event.preventDefault();
}
};
var json = Q.nfbind(d3.json);
function doPlayLists(channelId) {
stopVideo();
d3.select('#articles').selectAll('div').remove();
d3.select('#playListTitle').text('情報取得中..').style('color', 'blue');
var nextPageToken = '';
var fetching = false;
var playListItems = [];
function getPlayLists(pageToken) {
fetching = true;
var defer = Q.defer();
var pt = '';
if (pageToken) {
pt = '&pageToken=' + pageToken;
}
json('https://www.googleapis.com/youtube/v3/playlists?part=snippet&channelId=' + channelId + '&order=date&maxResults=50' + pt + '&key=' + youtube_apikey)
.then(function (d) {
Array.prototype.push.apply(playListItems, d.items);
nextPageToken = d.nextPageToken;
defer.resolve();
});
return defer.promise;
}
d3.select(window)
.on("scroll", maybeFetch)
.on("resize", maybeFetch);
function maybeFetch() {
if (!fetching && nextPageToken && d3.select("#article").node().getBoundingClientRect().top < window.innerHeight) {
getPlayLists(nextPageToken).then(displayPlayLists);
}
}
getPlayLists().then(displayPlayLists);
function displayPlayLists() {
fetching = false;
d3.select('#playListTitle').text('');
//var videos = result.filter(
// function (e, i, a) {
// return e.id.kind == 'youtube#video';
// });
var thumb = d3.select('#articles').selectAll('div')
.data(playListItems, function (d) { return d.id;})
.enter()
.append('div')
.classed({ 'col-xs-6': true,'col-sm-3': true, 'col-md-2': true, 'col-lg-2': true })
.append('div')
.classed('thumbnail', true)
.style('overflow', 'auto')
.style('min-height', '140px');
var anchor = thumb.append('a')
.attr('href', '#')
.attr('target', '_blank')
.on('click', function (d) {
d3.event.preventDefault();
doThumbnailVideos(d);
});
anchor.append('img')
.attr('src', function (d) { return d.snippet.thumbnails.high.url; })
.attr('alt', function (d) { return d.snippet.title; });
anchor.append('p')
.text(function (d) { return d.snippet.title; })
.classed('thumbnail-title', true)
.each(function (d) {
var o = d3.select(this.parentNode.parentNode);
var rect = o.node().getBoundingClientRect();
d3.select(this).style('top', '5px')
.style('left', '20px')
.style('width', (rect.width - 10) + 'px')
.style('height', '40px');
});
}
}
function doThumbnailVideos(d) {
var playlistID = d.id;
d3.select('#articles').selectAll('div').remove();
d3.select('#playListTitle')
.text(function (dt) { return d.snippet.title; });
var nextPageToken = '';
var fetching = false;
var videoItems = [];
function getData(pageToken) {
var defer = Q.defer();
fetching = true;
var pt = '';
if (pageToken) {
pt = '&pageToken=' + pageToken;
}
json('https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=' + playlistID + '&order=date&maxResults=50' + pt + '&key=' + youtube_apikey)
.then(function (d) {
defer.resolve(d);
});
return defer.promise;
}
d3.select(window)
.on("scroll", maybeFetch)
.on("resize", maybeFetch);
function maybeFetch() {
if (!fetching && nextPageToken && d3.select("#article").node().getBoundingClientRect().top < window.innerHeight) {
getData(nextPageToken).then(displayThumbnail);
}
}
function displayThumbnail(d) {
fetching = false;
nextPageToken = d.nextPageToken;
Array.prototype.push.apply(videoItems, d.items);
var vthumb = d3.select('#articles').selectAll('div')
.data(videoItems, function (d) { return d.id; })
.enter()
.append('div')
.classed({ 'col-xs-6': true, 'col-sm-3': true, 'col-md-2': true, 'col-lg-2': true })
.append('div')
.classed('thumbnail', true)
.style('overflow', 'auto')
.style('min-height','140px');
vthumb.append('img')
.attr('src', function (d) { return d.snippet.thumbnails.high.url; })
.attr('alt', function (d) { return d.snippet.title; });
vthumb.on('mouseenter', function (d) {
var o = d3.select(this);
var rect = o.node().getBoundingClientRect();
var p = d3.select('#playerObj');
player.setSize(rect.width, rect.height);
p.style('width', rect.width + 'px')
.style('height', rect.height + 'px')
.style('left', (rect.left + window.scrollX) + 'px')
.style('top', (rect.top + window.scrollY) + 'px')
.style('display', 'block');
player.loadVideoById({ videoId: d.snippet.resourceId.videoId });
d3.event.preventDefault();
});
vthumb.append('p')
.text(function (d) { return d.snippet.title; })
.classed('thumbnail-title', true)
.each(function (d) {
var o = d3.select(this.parentNode);
var rect = o.node().getBoundingClientRect();
d3.select(this).style('top', '5px')
.style('left', '20px')
.style('width', (rect.width - 10) + 'px')
.style('height', (rect.height - 10) + 'px');
});
}
// 初期設定
getData(nextPageToken)
.then(displayThumbnail);
}
doPlayLists('UCgwM0kBBsDRMZDhhWuTNR-g');
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment