/*
* JavaScript file jquery.text-on-youtube.js v0.8.1 last modified 11/01/02
*
* バージョン0.6-0.8のの追加・修正点:
* 自動再生とループ再生機能を実装。個別の動画でもグローバルオプションとしても指定でき、
* autoplayプロパティ、loopプロパティをそれぞれtrueにする
* YouTube Data APIを利用してコメント一覧を取得し、画面上で確認できる仕様に
* IEのブラウザ判別に非推奨のjQuery.browserを使っていたので、!jQuery.support.opacityに変更
* ユーザー側から呼び出せる関数destroyYouTubeObject、getYouTubeProperty、setYouTubeProperty、getCurrentState、getRecentErrorを追加
* destroyYouTubeObjectはYouTubeオブジェクトの指定した動画オブジェクトを削除する
* getCurrentStateはプレイヤーの状況を、getRecentErrorは直近のエラーを返す
* getYouTubePropertyはYouTubeオブジェクトの各プロパティを取得する。第1引数に動画ID、第2引数にプロパティ名を入れる
* setYouTubePropertyはYouTubeオブジェクトの各プロパティを設定する。第1引数に動画ID、第2引数にプロパティ名、第3引数に値を入れる
* YouTubeオブジェクトのプロパティ(読み取り専用)のうち、videoReadyプロパティは動画が準備完了したらtrueを返す
*
*/
/* 最低限必要なグローバル変数を宣言 */
var onYouTubePlayerReady, onYoutubeQualityChange, youtubeStateObserver, youtubeErrorChecker;
(function(jQuery) {
var youtube = {}, currentID = 1000, isPlainObject = jQuery.isPlainObject, isArray = jQuery.isArray, ceil = Math.ceil, floor = Math.floor,
requests = {}, settings, videoIDs, videoObjs, subtitles, timer;
/* プラグインを呼び出すメソッド */
jQuery.fn.textOnYoutube = function(videoID, options, subtitle) {
var self = this;
/* SWFObjectライブラリが読み込まれているかチェック */
if (typeof swfobject == 'undefined') return this;
/* 字幕データがJSONファイルの場合 */
if (isString(options) || isString(subtitle)) {
var jsonURL = isString(options) ? options : subtitle;
if (isString(options)) jQuery.getJSON(jsonURL, null, function(data, status) { self.textOnYoutube(videoID, data); });
else jQuery.getJSON(jsonURL, null, function(data, status) { self.textOnYoutube(videoID, options, data); });
return this;
}
/* デフォルトオプション。後でユーザーオプションに上書きされる */
settings = {
width: 640,
height: 360,
autoplay: false,
loop: false
};
/* オプションと字幕情報の設定 */
if (options) {
if (isPlainObject(options)) {
if (!options.videoID) {
settings = jQuery.extend(settings, options);
}
else subtitles = [options];
}
else if (isArray(options)) {
subtitles = jQuery.map(options, function(object, index) {
return jQuery.extend(true, {}, object);
});
}
}
if (subtitle) {
if (isArray(subtitle)) {
subtitles = jQuery.map(subtitle, function(object, index) {
return jQuery.extend(true, {}, object);
});
}
else subtitles = [subtitle];
}
/* 各動画の初期設定 */
videoID = !isPlainObject(videoID) ? videoID : [videoID];
if (isArray(videoID)) {
if (isPlainObject(videoID[0])) {
videoObjs = self.map(function(index, element) {
var result = null;
jQuery.each(videoID, function(index, object) {
if (element.id == object.elementID) {
result = {
videoID: object.videoID,
width: object.width || settings.width,
height: object.height || settings.height,
autoplay: object.autoplay || settings.autoplay,
loop: object.loop || settings.loop,
forceQuality: object.forceQuality,
uiWidgetClass: object.uiWidgetClass,
showComments: object.showComments
};
result.baseFontSize = object.baseFontSize || floor(10 * result.width / 320);
}
});
if (result === null) self.splice(index, 1);
return result;
});
}
else videoObjs = jQuery.map(videoID, function(videoID, index) {
return { videoID: videoID, width: settings.width, height: settings.height, baseFontSize: floor(10 * settings.width / 320),
autoplay: settings.autoplay, loop: settings.loop };
});
}
else videoObjs = [{ videoID: videoID, width: settings.width, height: settings.height, baseFontSize: floor(10 * settings.width / 320),
autoplay: settings.autoplay, loop: settings.loop }];
/* 個別に動画埋め込み処理 */
return this.each(function(index) {
if (!videoObjs[index]) return;
var videoObj = videoObjs[index], videoID = videoObj.videoID || 'video-' + currentID, cacheID = currentID,
width = videoObj.width, height = videoObj.height, uiWidgetClass = videoObj.uiWidgetClass || 'ui-widget-header',
mouseover = false, mute = false, unstarted = true, disabled = true, commentDisabled = true,
textLayer, toggle, commentTool, commentBox, control;
/* youtubeオブジェクトに各動画の情報が入る */
youtube[videoID] = {};
/* 字幕情報があればプロパティに入れる */
if (subtitles) {
jQuery.each(subtitles, function(index, object) {
if (object.videoID == videoID) youtube[videoID].subtitles = object;
});
}
/* 外枠のスタイル指定 */
jQuery(this).css({
width: width,
height: height
});
/* テキストレイヤー作成 */
jQuery(this)
.append( textLayer = jQuery('
', {
id: 'text-' + currentID,
'class': 'text_layer',
css: {
fontSize: videoObj.baseFontSize,
width: width,
height: height
}
}) )
.append( jQuery('', {
id: 'replace-' + currentID,
html: 'この動画を利用するためには、バージョン8以上のFlashプレイヤーとJavaScriptを有効にする必要があります。'
}) );
/* YouTubeオブジェクトの各種プロパティ初期化 */
jQuery.extend(youtube[videoID], {
autoplay: videoObj.autoplay,
loop: videoObj.loop,
textLayer: textLayer.get(0),
currentIndex: 0,
currentTime: 0,
userQuality: videoObj.forceQuality || false,
currentQuality: 'default',
availableQualityLevels: [],
totalTime: 0,
autoSlide: true,
commentIndex: 0,
showComments: videoObj.showComments,
currentState: 'unstarted',
videoReady: false,
errorState: 'none',
mousedown: false
});
/* コメント表示するならばYouTube Data APIにリクエストする */
if (youtube[videoID].showComments) {
requests[videoID] = {};
initComments(videoID);
}
/* テキストレイヤー+コントロールと動画レイヤーの切り替えボタン作成 */
jQuery(this)
.append(
toggle = jQuery('', {
id: 'toggle-' + currentID,
css: {
opacity: 0,
zIndex: 1000,
left: 4,
top: 4,
width: 24,
height: 14
},
title: 'レイヤー切り替え',
html: 'toggle'
}).hide()
.button({ icons: { primary: 'ui-icon-newwin' }, text: false })
.click(function(e) {
if (!disabled) {
disabled = true;
control.animate({ opacity: 0 }, 'fast').hide(0);
if (youtube[videoID].showComments && requests[videoID]) {
commentTool.animate({ opacity: 0 }, 'fast').hide(0);
commentBox.animate({ opacity: 0 }, 'fast').hide(0);
}
jQuery(youtube[videoID].textLayer).css({ zIndex: 1 });
}
else {
disabled = false;
control.show(0).animate({ opacity: 0.9 }, 'fast');
if (youtube[videoID].showComments && requests[videoID]) {
commentTool.show(0).animate({ opacity: 0.9 }, 'fast');
if (!commentDisabled) commentBox.show(0).animate({ opacity: 0.7 }, 'fast');
}
jQuery(youtube[videoID].textLayer).css({ zIndex: 3 });
}
})
);
/* コメント表示ボタン作成 */
textLayer
.append(
commentTool = jQuery('', {
id: 'comment-tool-' + currentID,
'class': 'comment_tool',
css: {
opacity: 0.9,
left: (width - 106),
top: 4
}
}).hide()
.append(
jQuery('', { id: 'toggle-comment' + currentID, css: { marginRight: 12 }, title: 'コメント表示/非表示', html: 'toggle-comment' })
.button({ icons: { primary: 'ui-icon-comment' }, text: false })
.click(function(e) {
if (youtube[videoID].showComments && requests[videoID]) {
if (commentDisabled) {
commentDisabled = false;
var comments = requests[videoID].comments,
reg = /(^@\w*)\s([\w\W]*)$/,
time = comments[youtube[videoID].commentIndex].time.match(/([0-9\-]*)T([0-9:]*)/) || [],
comment, reply, rest;
jQuery(this).next().button('enable').next().button('enable');
if (youtube[videoID].commentIndex === 0) jQuery(this).next().button('disable');
else if (youtube[videoID].commentIndex == comments.length - 1) jQuery(this).next().next().button('disable');
comment = comments[youtube[videoID].commentIndex].content;
reply = comment.match(reg) ? comment.match(reg)[1].slice(1) : null;
rest = comment.match(reg) ? comment.match(reg)[2] : null;
comment = reply !== null ? reply + 'さんへ
' + rest : comment;
commentBox.html(
'' +
comment
);
commentBox.show(0).animate({ opacity: 0.7 }, 'fast');
}
else {
commentDisabled = true;
jQuery(this).next().button('disable').next().button('disable');
commentBox.animate({ opacity: 0 }, 'fast').hide(0);
}
}
})
)
.append(
jQuery('', { id: 'prev-comment' + currentID, css: { marginRight: 8 }, title: '前のコメント', html: 'prev-comment' })
.button({ disabled: true, icons: { primary: 'ui-icon-circle-arrow-w' }, text: false })
.click(function(e) {
if (youtube[videoID].showComments && requests[videoID]) {
var comments = requests[videoID].comments,
reg = /(^@\w*)\s([\w\W]*)$/,
time = comments[youtube[videoID].commentIndex].time.match(/([0-9\-]*)T([0-9:]*)/) || [],
comment, reply, rest;
youtube[videoID].commentIndex--;
if (youtube[videoID].commentIndex < 0) youtube[videoID].commentIndex = 0;
else if (youtube[videoID].commentIndex === 0) jQuery(this).button('disable');
else if (youtube[videoID].commentIndex == comments.length - 2) jQuery(this).next().button('enable');
comment = comments[youtube[videoID].commentIndex].content;
reply = comment.match(reg) ? comment.match(reg)[1].slice(1) : null;
rest = comment.match(reg) ? comment.match(reg)[2] : null;
comment = reply !== null ? reply + 'さんへ
' + rest : comment;
commentBox.html(
'' +
comment
);
}
})
)
.append(
jQuery('', { id: 'next-comment' + currentID, title: '次のコメント', html: 'next-comment' })
.button({ disabled: true, icons: { primary: 'ui-icon-circle-arrow-e' }, text: false })
.click(function(e) {
if (youtube[videoID].showComments && requests[videoID]) {
var comments = requests[videoID].comments,
reg = /(^@\w*)\s([\w\W]*)$/,
time = comments[youtube[videoID].commentIndex].time.match(/([0-9\-]*)T([0-9:]*)/) || [],
comment, reply, rest;
youtube[videoID].commentIndex++;
if (youtube[videoID].commentIndex > comments.length - 1) youtube[videoID].commentIndex = comments.length - 1;
else if (youtube[videoID].commentIndex == comments.length - 1) jQuery(this).button('disable');
else if (youtube[videoID].commentIndex == 1) jQuery(this).prev().button('enable');
comment = comments[youtube[videoID].commentIndex].content;
reply = comment.match(reg) ? comment.match(reg)[1].slice(1) : null;
rest = comment.match(reg) ? comment.match(reg)[2] : null;
comment = reply !== null ? reply + 'さんへ
' + rest : comment;
commentBox.html(
'' +
comment
);
}
})
)
);
/* コメントボックス作成 */
textLayer
.append( commentBox = jQuery('', {
id: 'comment-box-' + currentID,
'class': 'comment_box',
css: {
opacity: 0,
left: '8%',
width: floor(width * 0.76),
height: floor(150 * height / 180 - 85)
}
}).hide() );
/* コントロール部分の作成 */
jQuery(this)
.append( control = jQuery('', {
id: 'control-' + currentID,
'class': 'video_control ' + uiWidgetClass,
css: {
opacity: 0,
left: 4,
top: (height - 36),
width: (width - 30),
height: 24
}
}).hide() )
.hover(
function(e) {
if (!unstarted) toggle.show(0).animate({ opacity: 0.9 }, 'fast');
if (!disabled && this != e.relatedTarget && !jQuery.contains(this, e.relatedTarget)) {
if (youtube[videoID].showComments) {
commentTool.show(0).animate({ opacity: 0.9 }, 'fast');
if (!commentDisabled) commentBox.show(0).animate({ opacity: 0.7 }, 'fast');
}
control.show(0).animate({ opacity: 0.9 }, 'fast');
}
},
function(e) {
if (!unstarted) toggle.animate({ opacity: 0 }, 'fast').hide(0);
if (!disabled && this != e.relatedTarget && !jQuery.contains(this, e.relatedTarget)) {
if (youtube[videoID].showComments) {
commentTool.animate({ opacity: 0 }, 'fast').hide(0);
commentBox.animate({ opacity: 0 }, 'fast').hide(0);
}
control.animate({ opacity: 0 }, 'fast').hide(0);
}
}
);
/* コントロールの各種ボタン作成 */
control
.append( jQuery('', { id: 'play-pause' + currentID, css: { marginRight: 4 }, title: '再生/ポーズ', html: 'play-pause' }) )
.append( jQuery('', { id: 'stop-' + currentID, css: { marginRight: 4 }, title: '停止', html: 'stop' }) )
.append( jQuery('', { id: 'seek-prev' + currentID, css: { marginRight: 4 }, title: '巻き戻し', html: 'prev-seek' }) )
.append( jQuery('', { id: 'seek-next' + currentID, css: { marginRight: 4 }, title: '早送り', html: 'next-seek' }) )
.append( jQuery('', { id: 'volume-' + currentID, css: { marginRight: 8 }, title: '音量オン/オフ', html: 'next-seek' }) )
.append( jQuery('', { id: 'current-time-' + currentID, css: { verticalAlign: 'middle', fontSize: '12px' }, html: '--' }) )
.append( jQuery('', { css: { verticalAlign: 'middle', fontSize: '12px' }, html: ' / ' }) )
.append( jQuery('', { id: 'total-time-' + currentID, css: { verticalAlign: 'middle', fontSize: '12px' }, html: '--' }) )
.append( jQuery('', { id: 'slider-' + currentID, 'class': 'video_slider', css: { width: (width - 270), left: 250, top: 8 } }) );
/* 各ボタンにアイコンやイベントを設定 */
control.children('button[id^=play-pause]').button({
icons: { primary: 'ui-icon-play' },
text: false
})
.click(function(e) {
if (youtube[videoID].currentState == 'playing') {
youtube[videoID].player.pauseVideo();
jQuery(this).button('option', 'icons', { primary: 'ui-icon-play' });
}
else if (youtube[videoID].currentState == 'paused' ||
youtube[videoID].currentState == 'video-cued') {
youtube[videoID].player.playVideo();
jQuery(this).button('option', 'icons', { primary: 'ui-icon-pause' });
}
else if (youtube[videoID].currentState == 'ended' ||
youtube[videoID].currentState == 'unstarted') {
youtube[videoID].player.seekTo(0);
if (youtube[videoID].subtitles) resetSubtitles(videoID);
jQuery(this).button('option', 'icons', { primary: 'ui-icon-pause' });
}
})
.next().button({
icons: { primary: 'ui-icon-stop' },
text: false
})
.click(function(e) {
youtube[videoID].player.stopVideo();
if (youtube[videoID].subtitles) resetSubtitles(videoID);
jQuery(this).prev().button('option', 'icons', { primary: 'ui-icon-play' });
})
.next().button({
icons: { primary: 'ui-icon-seek-prev' },
text: false
})
.mousedown(function(e) {
youtube[videoID].mousedown = true;
var timer = setInterval(function() {
if (!youtube[videoID].mousedown) clearInterval(timer);
var currentTime = youtube[videoID].player.getCurrentTime(),
totalTime = youtube[videoID].totalTime;
youtube[videoID].player.seekTo(currentTime - ceil(totalTime / 30));
if (youtube[videoID].subtitles) seekCurrentSubtitle(videoID);
}, 500);
})
.mouseup(function(e) {
youtube[videoID].mousedown = false;
})
.next().button({
icons: { primary: 'ui-icon-seek-next' },
text: false
})
.mousedown(function(e) {
youtube[videoID].mousedown = true;
var timer = setInterval(function() {
if (!youtube[videoID].mousedown) clearInterval(timer);
var currentTime = youtube[videoID].player.getCurrentTime(),
totalTime = youtube[videoID].totalTime;
youtube[videoID].player.seekTo(currentTime + ceil(totalTime / 30));
if (youtube[videoID].subtitles) seekCurrentSubtitle(videoID);
}, 300);
})
.mouseup(function(e) {
youtube[videoID].mousedown = false;
})
.next().button({
icons: { primary: 'ui-icon-volume-on' },
text: false
})
.click(function(e) {
if (!mute) {
mute = true;
jQuery(this).button('option', 'icons', { primary: 'ui-icon-volume-off' });
youtube[videoID].player.mute();
}
else {
mute = false;
jQuery(this).button('option', 'icons', { primary: 'ui-icon-volume-on' });
youtube[videoID].player.unMute();
}
})
.siblings('div:first').slider({
start: function(event, ui) {
youtube[videoID].autoSlide = false;
},
stop: function(event, ui) {
youtube[videoID].autoSlide = true;
youtube[videoID].player.seekTo(floor(youtube[videoID].totalTime * ui.value / 100));
if (youtube[videoID].subtitles) seekCurrentSubtitle(videoID);
}
})
.progressbar({ value: 0 })
.children('a:first').css({ width: 10, height: 24, top: '-0.5em' });
/* 動画が始まったら動画レイヤーからテキストレイヤーに切り替える */
youtube[videoID].waitTimer = setInterval(jQuery.proxy(function() {
if (youtube[videoID].currentState == 'playing') {
textLayer.css({ zIndex: 3 });
if (youtube[videoID].showComments) {
commentTool.show(0).animate({ opacity: 0.9 }, 'fast');
}
control.show(0).animate({ opacity: 0.9 }, 'fast');
toggle.show(0).animate({ opacity: 0.9 }, 'fast');
unstarted = false;
disabled = false;
jQuery('#control-' + cacheID + '> button').first().button('option', 'icons', { primary: 'ui-icon-pause' });
clearInterval(youtube[videoID].waitTimer);
}
}, this), 100);
/* SWFObjectライブラリを呼び出して動画を埋め込む */
var params = { allowScriptAccess: 'always', wmode: 'transparent' },
atts = { id: videoID, 'class': 'video_layer' };
swfobject.embedSWF('http://www.youtube.com/apiplayer?enablejsapi=1&playerapiid=' + videoID + '&version=3',
'replace-' + currentID, width, height, '8', null, null, params, atts);
currentID++;
});
};
jQuery.getYouTubeProperty = function(videoID, propname) {
if (youtube[videoID]) {
return youtube[videoID][propname];
}
};
jQuery.setYouTubeProperty = function(videoID, propname, propvalue) {
if (youtube[videoID]) {
youtube[videoID][propname] = propvalue;
}
};
jQuery.getCurrentState = function(videoID) {
return youtube[videoID] ? youtube[videoID].currentState : false;
};
jQuery.getRecentError = function(videoID) {
return youtube[videoID] ? youtube[videoID].errorState : false;
};
/* 指定した動画IDのオブジェクトをYouTubeオブジェクトから完全削除する。trueを渡すとYouTubeオブジェクトに登録してあるすべての動画オブジェクトを削除する */
jQuery.destroyYouTubeObject = function(videoID) {
if (youtube) {
if (videoID === true) {
jQuery.each(youtube, function(key, value) {
if (youtube[key].executer) clearInterval(youtube[key].executer);
if (youtube[key].waitTimer) clearInterval(youtube[key].waitTimer);
delete youtube[key];
delete requests[key];
});
}
else if (isArray(videoID)) {
jQuery.each(videoID, function(index, id) {
if (youtube[id].executer) clearInterval(youtube[id].executer);
if (youtube[id].waitTimer) clearInterval(youtube[id].waitTimer);
delete youtube[id];
delete requests[id];
});
}
else {
if (youtube[videoID].executer) clearInterval(youtube[videoID].executer);
if (youtube[videoID].waitTimer) clearInterval(youtube[videoID].waitTimer);
delete youtube[videoID];
delete requests[videoID];
}
}
};
/* 動画プレイヤーの準備ができたら呼び出されるコールバック */
onYouTubePlayerReady = function(playerID) {
youtube[playerID].player = document.getElementById(playerID);
initVideo(playerID);
};
/* 動画の画質が変更されたら呼び出されるコールバック */
onYoutubeQualityChange = function(quality, videoID) {
youtube[videoID].currentQuality = quality;
};
/* 動画プレイヤーの状態が変更されたら呼び出されるコールバック */
youtubeStateObserver = function(state, videoID) {
if (youtube[videoID].currentState == 'unstarted' && state == 5) {
youtube[videoID].currentState = 'video-cued';
}
else if (state == -1) {
youtube[videoID].currentState = 'unstarted';
}
else if (state === 0) {
youtube[videoID].currentState = 'ended';
}
else if (state == 1) {
youtube[videoID].currentState = 'playing';
}
else if (state == 2) {
youtube[videoID].currentState = 'paused';
}
};
/* 動画プレイヤーでエラーが発生したら呼び出されるコールバック */
youtubeErrorChecker = function(code, videoID) {
if (code == 100) {
youtube[videoID].errorState = 'not-found';
}
else if (code === 101 || code === 150) {
youtube[videoID].errorState = 'not-allowed';
}
};
/* プレイヤー準備終了後に初期設定を行う */
function initVideo(videoID) {
if (!youtube[videoID].player) {
if (timer) clearTimeout(timer);
timer = setTimeout(curryTimerCallback(initVideo, videoID), 30);
return;
}
else if (requests[videoID] &&
(requests[videoID].state != 'finished' && requests[videoID].state != 'failed')) {
if (timer) clearTimeout(timer);
timer = setTimeout(curryTimerCallback(initVideo, videoID), 30);
return;
}
var yt = youtube[videoID],
element = jQuery('object[id=' + videoID + ']').parent(),
control = element.children('.video_control:first'),
slider = control.children('div:first'),
bar = slider.children('div:first').css({ position: 'absolute' }),
sliderWidth = (control.width() - 270), currentTime = 0, totalTime = 0,
videoBytesTotal = 0, videoStartBytes = 0, videoBytesLoaded = 0, count = 0;
if (yt.subtitles) setupSubtitles(videoID);
yt.player.addEventListener('onStateChange', '(function(state) { youtubeStateObserver(state, "' + videoID + '"); })');
yt.player.addEventListener('onError', '(function(state) { youtubeErrorChecker(state, "' + videoID + '"); })');
yt.player.addEventListener('onPlaybackQualityChange', '(function(state) { onYoutubeQualityChange(state, "' + videoID + '"); })');
if (!yt.autoplay) yt.player.cueVideoById(videoID, 0);
else yt.player.loadVideoById(videoID, 0);
yt.videoReady = true;
yt.executer = setInterval(function() {
if (totalTime === 0) {
totalTime = yt.player.getDuration();
if (totalTime > 0) {
yt.currentQuality = yt.player.getPlaybackQuality();
yt.availableQualityLevels = yt.player.getAvailableQualityLevels();
if (yt.userQuality !== false) yt.player.setPlaybackQuality(yt.userQuality);
yt.totalTime = totalTime;
control.children('span:eq(2)')
.text(floor(totalTime / 60) + ':' + (totalTime % 60 < 10 ? '0' + totalTime % 60 : totalTime % 60));
}
}
else if (videoBytesTotal === 0) {
videoBytesTotal = yt.player.getVideoBytesTotal();
}
else {
currentTime = yt.player.getCurrentTime();
videoStartBytes = yt.player.getVideoStartBytes();
videoBytesLoaded = yt.player.getVideoBytesLoaded();
yt.currentTime = currentTime;
if (yt.currentState == 'ended') {
if (yt.subtitles) resetSubtitles(videoID);
if (yt.loop) yt.player.playVideo();
jQuery(this).button('option', 'icons', { primary: 'ui-icon-pause' });
yt.currentState = 'unstarted';
}
if (count % 15 === 0) {
var ct = floor(currentTime),
rate = floor(100 * ct / totalTime);
if (yt.autoSlide) {
control.children('span:first')
.text(floor(ct / 60) + ':' + (ct % 60 < 10 ? '0' + (ct % 60) : (ct % 60)));
slider.slider('value', rate);
}
slider.progressbar('value', floor(100 * (videoStartBytes + videoBytesLoaded) / videoBytesTotal));
}
if (yt.subtitles && yt.currentState == 'playing') {
updateSubtitles(videoID, currentTime);
}
}
count++;
}, 20);
}
/* 字幕情報のセットアップ */
function setupSubtitles(videoID) {
var subtitles = youtube[videoID].subtitles;
if (!isArray(subtitles.timeline)) subtitles.timeline = [subtitles.timeline];
subtitles.timeline.sort(function(a, b) {
var sa = a.time,
sb = b.time;
return sa < sb ? -1 : sa > sb ? 1 : 0;
});
jQuery.each(subtitles.timeline, function(index, subtitle) {
var element;
jQuery.extend(subtitle, {
state: 'idle',
duration: subtitle.duration || 1
});
jQuery(youtube[videoID].textLayer).append(
element = jQuery('', {
id: 'subtitle-' + index + '-' +videoID,
'class': 'video_subtitle',
css: subtitle.css
}).hide()
);
if (!jQuery.support.opacity) {
element.css({
filter: 'progid:DXImageTransform.Microsoft.Shadow(Color=#000000, Direction=135, Strength=1);'
});
}
if (subtitle.anchor) {
element.append(
jQuery('', {
id: 'anchor-' + index + '-' +videoID,
href: subtitle.anchor,
html: subtitle.text
})
);
}
else element.html(subtitle.text);
subtitle.element = element.get(0);
});
}
/* 動画の経過時間に合わせ字幕を表示する */
function updateSubtitles(videoID, currenttime) {
var yt = youtube[videoID], currentTime = currenttime || yt.player.getCurrentTime(), currentSubtitle;
if (yt.currentIndex < yt.subtitles.timeline.length) {
currentSubtitle = yt.subtitles.timeline[yt.currentIndex];
if (currentSubtitle.time <= currentTime) {
if (currentSubtitle.effects && currentSubtitle.state == 'idle') {
currentSubtitle.state = 'running';
jQuery(currentSubtitle.element).stop(true, false);
if (isArray(currentSubtitle.effects)) {
if (currentSubtitle.effects[0] != 'show' &&
currentSubtitle.effects[0] != 'fadeIn' &&
currentSubtitle.effects[0] != 'slideDown'
) jQuery(currentSubtitle.element).show(0);
jQuery(currentSubtitle.element)[currentSubtitle.effects[0]](
currentSubtitle.effects[1],
currentSubtitle.effects[2],
currentSubtitle.effects[3],
currentSubtitle.effects[4]
);
}
else jQuery(currentSubtitle.element)[currentSubtitle.effects](0);
}
yt.currentIndex++;
}
}
jQuery.each(yt.subtitles.timeline, function(index, subtitle) {
if (subtitle.effects && subtitle.state == 'running') {
if (currentTime > subtitle.time + subtitle.duration) {
subtitle.state = 'finished';
jQuery(subtitle.element).hide(0);
}
}
});
}
/* 字幕設定を動画スタート時に戻す */
function resetSubtitles(videoID) {
var yt = youtube[videoID], currentTime = yt.player.getCurrentTime(), currentSubtitle;
yt.currentIndex = 0;
jQuery.each(yt.subtitles.timeline, function(index, subtitle) {
subtitle.state = 'idle';
jQuery(subtitle.element).stop(true, false).hide(0).css(subtitle.css);
});
}
/* 字幕設定を指定された時間に合わせて変更する */
function seekCurrentSubtitle(videoID) {
var yt = youtube[videoID], currentTime = yt.player.getCurrentTime(),
timeline = yt.subtitles.timeline, lastSubtitle = timeline[timeline.length - 1], isFoundSubtitle = false;
if (currentTime > lastSubtitle.time + lastSubtitle.duration) {
yt.currentIndex = timeline.length;
jQuery.each(timeline, function(index, subtitle) {
subtitle.state = 'finished';
jQuery(subtitle.element).stop(true, false).hide(0).css(subtitle.css);
});
}
else {
yt.currentIndex = 0;
jQuery.each(timeline, function(index, subtitle) {
jQuery(subtitle.element).stop(true, false).hide(0).css(subtitle.css);
if (!isFoundSubtitle) {
if (currentTime <= subtitle.time + subtitle.duration) {
subtitle.state = 'idle';
isFoundSubtitle = true;
yt.currentIndex = index;
}
else subtitle.state = 'finished';
}
else subtitle.state = 'idle';
});
}
}
/* YouTube Data APIから動画フィードを引っ張ってくる。リクエストはすべてData API version2に */
function initComments(videoID) {
requests[videoID] = jQuery.extend(requests[videoID], {
feedurl: '',
state: 'idle',
title: '',
comments: [],
totalResults: 0,
countHint : 0
});
jQuery.getJSON(
'http://gdata.youtube.com/feeds/api/videos/' + videoID + '?callback=?',
{ 'alt': 'json-in-script', 'v': 2 },
recursivelyAjaxCallback
);
}
/* コメントフィードをmaxCommentsに達するまで繰り返しリクエストする */
function recursivelyAjaxCallback(data, status) {
var maxComments = 100, maxResults = 20;
if (data.feed) {
var feed = data.feed,
reg = /.*:video:([\w\-]*):comments$/,
id = feed.id.$t.match(reg)[1],
comments = feed.entry || [],
request;
request = requests[id];
jQuery.merge(
request.comments, jQuery.map(comments, function(object, index) {
var name = object.author[0].name.$t;
return {
name: name,
time: object.updated.$t,
content: object.content.$t,
link: 'http://www.youtube.com/user/' + name
};
})
);
}
else {
/* 最初は動画フィードを処理する */
var entry = data.entry,
reg = /.*:video:([\w\-]*)$/,
id = entry.id.$t.match(reg)[1],
title = entry.title.$t,
request, feedurl, countHint;
request = requests[id];
if (!entry.gd$comments) {
request.state = 'failed';
return;
}
feedurl = entry.gd$comments.gd$feedLink.href.match(/(.*comments)\?v=2$/)[1];
countHint = entry.gd$comments.gd$feedLink.countHint;
request.state = 'running';
request.title = title;
request.feedurl = feedurl;
request.countHint = countHint;
}
if (maxComments <= request.totalResults || request.countHint <= request.totalResults) {
request.state = 'finished';
return;
}jQuery.getJSON(
request.feedurl + '?callback=?',
{
'max-results': maxResults,
'start-index': request.totalResults + 1,
'alt': 'json-in-script',
'v': 2
},
recursivelyAjaxCallback
);
request.totalResults += maxResults;
}
/* ユーティリティ関数 */
function curryTimerCallback() {
var userFunc = arguments[0],
args = Array.prototype.slice.call(arguments, 1);
return function() {
return userFunc.apply(this, args);
};
}
function isString(object) {
return Object.prototype.toString.call(object) == '[object String]';
}
})(jQuery);