Skip to content

Instantly share code, notes, and snippets.

@sfpgmr
Last active May 25, 2016 20:24
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/39f6fc206606353d2222 to your computer and use it in GitHub Desktop.
Save sfpgmr/39f6fc206606353d2222 to your computer and use it in GitHub Desktop.
YouTube Viewer Sample

このサンプルについて

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

使い方

  1. 検索したいタイプ(Channel,Video,Playlist)を選択します。
  2. 検索ボックスにキーワードを入力し、エンターキーを押すと検索結果をサムネイルで表示します。
  3. チャンネルやプレイリストをクリックすると動画一覧を表示します。動画一覧にマウスカーソルをポイントするとプレビュー再生します。

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

http://bl.ocks.org/sfpgmr/raw/39f6fc206606353d2222/

使用したライブラリ

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

##ライセンス

  • MITライセンス

##リンク

<!DOCTYPE html>
<html vocab="http://schema.org">
<head>
<title>YouTube Viewer</title>
<meta charset="utf-8" />
<meta name="description" content="検索したいタイプ(Channel,Video,Playlist)を選択、検索ボックスにキーワードを入力し、エンターキーを押すと検索結果をサムネイルで表示します。チャンネルやプレイリストをクリックすると動画一覧を表示します。動画一覧にマウスカーソルをポイントするとプレビュー再生します。" />
<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 navbar-toggle-sm 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><form class="navbar-form navbar-left" role="search" id="search"><div class="form-group"><div class="input-group input-group-sm"><select id="type" style="width:100px" class="form-control"><option value="channel">Channel</option><option value="video">Video</option><option value="playlist">PlayList</option></select><input id="keyword" style="width:160px" type="text" class="form-control" placeholder="キーワードを入力" /></div></div></form></div>
</div>
<div class="collapse navbar-collapse" id="navbar-collapse">
<p class="navbar-text navbar-text-sm 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>YouTube Viewer</h2>
<p>検索したいタイプ(Channel,Video,Playlist)を選択、検索ボックスにキーワードを入力し、エンターキーを押すと検索結果をサムネイルで表示します。チャンネルやプレイリストをクリックすると動画一覧を表示します。動画一覧にマウスカーソルをポイントするとプレビュー再生します。</p>
<ol class="breadcrumb" id="breadcrumb">
<li>test</li>
</ol>
</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-17T01:47:04.299Z">2015-01-17T01:47:04.299Z</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;
var fetchController;
// Youtube Player APIのロード
d3.select('head').append('script').attr('src', '//www.youtube.com/iframe_api');
function onYouTubeIframeAPIReady() {
var player_ = new YT.Player('player', {
width: playerWidth,
height: playerHeight,
playerVars: {
'fs': 1,
'hd': 1
},
events: {
'onReady': onPlayerReady
},
enablejsapi: '1'
});
function onPlayerReady(event) {
player = player_;
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');
}
};
function previewVideo(this_,videoId) {
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: videoId });
d3.event.preventDefault();
};
if (window.location.href.match(/bl\.ocks\.org/i)) {
d3.select('#homeLink').attr('href', '//bl.ocks.org/sfpgmr/');
}
var keywordSelection = d3.select('#keyword');
var typeSelection = d3.select('#type');
var breadcrumbSelection = d3.select('#breadcrumb');
keywordSelection.node().value = 'SFPG';
//d3.select('#displayPlayLists').on('click', function () {
// d3.event.preventDefault();
// doPlayLists(d3.select('#channelID').node().value);
//});
var typeJumpTable = {
channel: doChannelList,
video: doVideoList,
playlist: doPlayList
}
function query() {
var type = typeSelection.node();
var typeValue = type.options[type.selectedIndex].value;
typeJumpTable[typeValue](keywordSelection.node().value);
breadcrumbSelection.selectAll('li').remove();
breadcrumbSelection
.append('li')
.append('a')
.attr('href', '#')
.text(typeValue)
.on('click', function () {
d3.event.preventDefault();
query();
})
breadcrumbSelection
.append('li')
.classed('active', true)
.text(keywordSelection.node().value);
};
d3.select('#search').on('submit', function () {
d3.event.preventDefault();
query();
});
var json = Q.nfbind(d3.json);
var apiEntryPonint = 'https://www.googleapis.com/youtube/v3/';
// Youtube APIの呼び出し
function callYoutubeDataAPI(command, params) {
var queryString = '';
for (var p in params) {
queryString += p + '=' + encodeURIComponent(params[p]) + '&';
}
queryString += 'key=' + youtube_apikey;
var defer = Q.defer();
json(apiEntryPonint + command + '?' + queryString)
.then(function (d) {
defer.resolve(d);
});
return defer.promise;
}
// データフェッチ制御
function FetchController(apiFunc, resultFunc, doNotAutoFetch) {
this.nextPageToken = '';
this.fetching = false;
this.apiFunc = apiFunc;
this.resultFunc = resultFunc;
if (!doNotAutoFetch) {
d3.select(window)
.on("scroll", this.maybeFetch.bind(this))
.on("resize", this.maybeFetch.bind(this));
}
}
FetchController.prototype = {
maybeFetch: function () {
if (!this.fetching && this.nextPageToken
&& d3.select("#article").node().getBoundingClientRect().top < window.innerHeight) {
this.fetch(this.nextPageToken);
}
},
fetch: function () {
var this_ = this;
this_.fetching = true;
this_.apiFunc(this_.nextPageToken)
.then(function (result) {
this_.nextPageToken = (!result.nextPageToken) ? '' : result.nextPageToken;
Q(this_.resultFunc(result)).then(function () { this_.fetching = false; });
});
}
}
// サムネイル追加
function appendThumbnail(selection) {
return selection.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', '125px');
}
// タイトル表示追加
function appendTitle(selection) {
return selection
.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', '40px');
});
}
// イメージ追加
function appendImage(selection) {
return selection.append('img')
.attr('src', function (d) { return d.snippet.thumbnails.default.url; })
.attr('alt', function (d) { return d.snippet.title; })
.style({'width':'auto','height':'110px'});
}
// キーワードからチャンネル一覧を作成
function doChannelList(keyword) {
stopVideo();
d3.select('#articles').selectAll('div').remove();
var channelList = [];
fetchController = new FetchController(
function (pageToken) {
return callYoutubeDataAPI('search',
{
part: 'snippet',
type: 'channel',
q: keyword,
order: 'date',
maxResults: '50',
'pageToken': pageToken
}
);
}
, function (result) {
Array.prototype.push.apply(channelList, result.items);
appendThumbnail(d3.select('#articles').selectAll('div')
.data(channelList, function (d) { return d.id.channelId; })
.enter())
.append('a')
.attr('href', '#')
.attr('target', '_blank')
.on('click', function (d) {
d3.event.preventDefault();
breadcrumbSelection
.append('li')
.classed('active', true)
.append('a').attr('href','#')
.text('Videos')
.on('click', function () {
var last = breadcrumbSelection.select('li:last-child');
if (last.text() != 'PlayLists') {
last.remove();
}
doVideoList(null,d.id.channelId);}
);
doVideoList(null, d.id.channelId);
})
.call(appendImage)
.call(appendTitle);
});
fetchController.fetch();
}
// 公開動画一覧を表示
function doVideoList(keyword,channelId) {
stopVideo();
d3.select('#articles').selectAll('div').remove();
if (!keyword) keyword = '';
if (!channelId) { channelId = '' }
else {
if (breadcrumbSelection.select('li:last-child').text() != 'PlayLists') {
breadcrumbSelection
.append('li')
.attr('data-type', 'Playlists')
.classed('active', false)
.append('a').attr('href', '#')
.text('PlayLists')
.on('click', function () {
var last = breadcrumbSelection.select('li:last-child');
if (last.text() != 'PlayLists') {
last.remove();
}
doPlayList(null, channelId);
});
}
};
var videoList = [];
fetchController = new FetchController(
function (pageToken) {
return callYoutubeDataAPI('search',
{
part: 'snippet',
type: 'video',
'channelId':channelId,
q: keyword,
order: 'date',
maxResults: '50',
'pageToken': pageToken
}
);
}
, function (result) {
Array.prototype.push.apply(videoList, result.items.filter(function (d) {
return d.snippet.thumbnails;
}));
appendThumbnail(d3.select('#articles').selectAll('div')
.data(videoList, function (d) { return d.id.videoId; })
.enter())
.call(appendImage)
.call(appendTitle)
.on('mouseenter', function (d) {
previewVideo(this, d.id.videoId);
});
});
fetchController.fetch();
}
// プレイリストを表示
function doPlayList(keyword,channelId) {
stopVideo();
if (!keyword) keyword = '';
if (!channelId) { channelId = '' }
d3.select('#articles').selectAll('div').remove();
d3.select('#playListTitle').text('情報取得中..').style('color', 'blue');
fetchController = new FetchController(
function (pageToken) {
return callYoutubeDataAPI(
'search'
, { part: 'snippet',type:'playlist', channelId : channelId,q: keyword, order: 'date', maxResults: '50', pageToken: pageToken }
);
},
displayPlayLists);
var playListItems = [];
fetchController.fetch();
function displayPlayLists(result) {
Array.prototype.push.apply(playListItems, result.items);
d3.select('#playListTitle').text('');
appendThumbnail(d3.select('#articles').selectAll('div')
.data(playListItems, function (d) { return d.id.playlistId; })
.enter())
.append('a')
.attr('href', '#')
.attr('target', '_blank')
.on('click', function (d) {
d3.event.preventDefault();
breadcrumbSelection
.append('li')
.classed('active',true)
.text(d.snippet.title);
doThumbnailVideos(d);
})
.call(appendTitle)
.call(appendImage);
}
}
function doThumbnailVideos(d) {
var playlistID = d.id.playlistId;
d3.select('#articles').selectAll('div').remove();
d3.select('#playListTitle')
.text(function (dt) { return d.snippet.title; });
var videoItems = [];
fetchController = new FetchController(
function (pageToken) {
return callYoutubeDataAPI(
'playlistItems'
, { part: 'snippet', 'playlistId': playlistID, order: 'date', maxResults: '50', 'pageToken': pageToken }
);
},
displayThumbnail
);
fetchController.fetch();
function displayThumbnail(d) {
Array.prototype.push.apply(videoItems, d.items.filter(function (d) {
return !(!d.snippet.thumbnails);
}));
appendThumbnail(d3.select('#articles').selectAll('div')
.data(videoItems, function (d) { return d.id; })
.enter())
.call(appendImage)
.on('mouseenter', function (d) {
previewVideo(this, d.snippet.resourceId.videoId);
})
.call(appendTitle);
}
}
query();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment