
ニコニコ動画のプレイヤーがついにFlashからHTML5になりました。若干軽くなったかな?Flashだと複数ウィンドウで動かすと雪だるま式に重くなっていたので、ちょっと嬉しい。
というわけで、Greasemonkeyもこれに対応。
UIというかもうブログパーツだけなので、僕だけが便利に使えるGreasemonkeyになってますけど、一応報告しておきます。
既知の問題
- ページ遷移時にブログパーツが書き換わりません
要素の変化を監視する部分が上手く動いていないらしく、ページ内のリンクをクリックして動画を移動しても、ブログパーツが書き換わりません。お手数ですが、ページを再読込して下さい。
技術的な話
恐らく「DOMNodeInserted」が上手く動いていないんだと思うんですよ。どうせdeprecatedだったしいいんですけど、その代わりとして使えるはずの「MutationObserver」を僕が上手く使えてなくて、すみません対応出来てません。どうしたらいいんかなーインストール
インストールは下記URLをクリックしてください。https://gist.github.com/raw/3905580/nicoplayer_ui_customize.user.js
特徴
このGreasemonkeyでカスタマイズされる点は下記の通り。- ブログパーツをデフォルトで表示する
HTML5版動画プレイヤー対応ですが、FLASHにも対応しています。
2.0.6 → 3.0.2
- HTML5版動画プレイヤーに対応
ソースコード
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name NicoPlayer UI Customize | |
// @namespace http://1ni.co/greasemonkeys | |
// @description ニコニコ動画プレイヤーのUIをカスタマイズするGreasemonkey | |
// @version 3.0.2 | |
// @downloadURL https://gist.github.com/raw/3905580/nicoplayer_ui_customize.user.js | |
// @updateURL https://gist.github.com/raw/3905580/nicoplayer_ui_customize.user.js | |
// @include http://www.nicovideo.jp/watch/* | |
// @grant GM_getValue | |
// @grant GM_setValue | |
// @author Ippei "is" Suzuki | |
// @license MIT License; http://en.wikipedia.org/wiki/MIT_License | |
// ==/UserScript== | |
// Version History: | |
// 0.0.1 - 2012/05/04 リリース | |
// 0.1.0 - 2012/05/04 再生数/コメント数/マイリスト数を投稿者情報の下に | |
// 0.1.1 - 2012/05/04 投稿者名の見栄えを改善 | |
// 0.2.0 - 2012/05/05 投稿日時をファーストビューに表示 | |
// 0.2.1 - 2012/05/05 ページ遷移した場合に投稿者情報が更新されない問題を修正 | |
// 0.2.2 - 2012/05/05 ページ遷移した場合の様々な問題を修正 | |
// 0.2.3 - 2012/06/09 [69の日] 文字サイズの微調整など | |
// 0.2.4 - 2012/06/18 プレイヤー下のHTML変更などに対応 | |
// 0.2.5 - 2012/06/19 投稿者情報のCSSを微調整 | |
// 0.2.6 - 2012/08/22 コメント欄下の広告を削除 | |
// 0.3.0 - 2012/10/17 「Q」に対応 | |
// 1.0.0 - 2012/10/17 表示方法を変更、バージョンを1.0.0に変更 | |
// 1.0.1 - 2012/10/18 コード整形とMetadataの変更 | |
// 1.0.2 - 2012/10/18 ブログパーツとプロフィール情報の位置を変更、動画IDを変更 | |
// 1.0.3 - 2012/12/20 デザイン変更で実装された機能を削除/ブログパーツ表示場所の変更 | |
// 2.0.0 - 2012/12/26 コントロールチップの採用 | |
// 2.0.1 - 2012/12/27 ブログパーツ表示場所の変更 | |
// 2.0.2 - 2012/12/29 未ログイン時にもブログパーツを表示するように | |
// 2.0.3 - 2012/12/29 表示個所、マウスオーバー時の振る舞いを微調整 | |
// 2.0.4 - 2013/02/16 動画情報のURLがthumb_watchからthumbに変わっていたのに対応 | |
// 2.0.5 - 2013/05/22 動画情報のURLがthumbからthumb_watchに戻っていたのに対応 | |
// 2.0.6 - 2013/07/25 Qwatchの更新に対応 / 動画説明見出しの表示可否削除 | |
// 3.0.0 - 2016/11/04 動画視聴ページ HTML5版(β)に対応 | |
// 3.0.1 - 2016/11/05 動画読み込み時の挙動を調整 | |
// 3.0.2 - 2016/11/05 チャンネル動画に対応 | |
// Copyrights: | |
// コードの一部を@toriimiyukkiさんのZeroFixからいただいています。 | |
// @toriimiyukkiさん記述以外の部分については@nobdoyplaceに所属します。 | |
(function() { | |
// version | |
var isHtml5 = (document.getElementsByTagName('html')[0].lang == 'ja') ? true | |
: false; | |
// settings | |
var bp = GM_getValue('showBlogParts', 0); // 0,1 | |
var vt = GM_getValue('showVideoTitleOnBlogParts', 0); // 0,1 | |
var ok = 'data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%08%00%00%00%08%08%03%00%00%00%F3%D1N%B9%00%00%00%04gAMA%00%00%AF%C87%05%8A%E9%00%00%00%19tEXtSoftware%00Adobe%20ImageReadyq%C9e%3C%00%00%00%0CPLTE%C2%E1g%EE%F7%D4t%98%3B%FF%FF%FFRq%08%BF%00%00%00%04tRNS%FF%FF%FF%00%40*%A9%F4%00%00%00%2CIDATx%DA%2C%CAQ%12%00%00%04%02%D1%AC%FB%DFY%C1%CF%AB%91%FAO%07%1F(%94F%C9%C1%CD%FA%85b6%C4%1D%C7%1E%01%06%00%16%B3%00%9A2%07c%95%00%00%00%00IEND%AEB%60%82'; | |
var ng = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAABGdBTUEAAK%2FINwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAJUExURfpgApsAAP%2F%2F%2F7DQ0cQAAAADdFJOU%2F%2F%2FANfKDUEAAAAhSURBVHjaYmCCAgZkBiMQghiMDEAIEWHAEIGqgQCAAAMAD54AayU%2BhVYAAAAASUVORK5CYII%3D'; | |
var blogPartsId = 'blogPartsArea'; | |
var tipId = 'CustomizeControlTip'; | |
// utility | |
var d = document; | |
var $ = function(name) { | |
return document.querySelector(name); | |
} | |
var ins = function(e, p) { | |
p.insertBefore(e, p.firstChild); | |
} | |
var isLogined = ($('#suggest_login')) ? false : true; | |
var htmlEscape = function(str) { | |
var map = { | |
"<" : "<", | |
">" : ">", | |
"&" : "&", | |
"'" : "'", | |
"\"" : """, | |
" " : " ", | |
" " : " " | |
}; | |
var replaceStr = function(s) { | |
return map[s]; | |
}; | |
return str.replace(/<|>|&|'|"|\s| /g, replaceStr); | |
} | |
var videoHeader = (isHtml5) ? $('.TagContainer') : $('#videoHeader'); | |
// video data | |
var videoId, profile, icon, expand, titleArea, title, author, videoStats, videoDate; | |
var initVideoData = function() { | |
// HTML5バージョン | |
if (isHtml5) { | |
var apiData = JSON.parse($('#js-initial-watch-data').dataset.apiData); | |
videoId = apiData.video.id; // コミュニティ/チャンネル対策 | |
// profile = $('#userProfile').cloneNode(true); | |
icon = (apiData.owner) ? apiData.owner.iconURL : ''; | |
// expand = $('.videoDetailExpand'); | |
// titleArea = expand.querySelector('.videoHeaderTitle'); | |
title = apiData.video.title; | |
author = (apiData.owner) ? apiData.owner.nickname : ''; | |
// videoStats = $('#videoStats').cloneNode(true); | |
videoDate = apiData.video.postedDateTime; | |
// Flashバージョン | |
} else { | |
videoId = $('.dicIcon a').href.match(/\/v\/(.+)$/)[1]; // コミュニティ/チャンネル対策 | |
profile = $('#userProfile').cloneNode(true); | |
icon = profile.querySelector('.usericon'); | |
expand = $('.videoDetailExpand'); | |
titleArea = expand.querySelector('.videoHeaderTitle'); | |
title = titleArea.innerHTML; | |
author = profile.querySelector('.userName').innerHTML; | |
videoStats = $('#videoStats').cloneNode(true); | |
videoDate = $('#videoInfoHead').querySelector('.videoPostedAt').innerHTML; | |
} | |
} | |
var initVideoDataNotLogined = function() { | |
videoId = location.href.match(/\/watch\/(.+)$/)[1]; // コミュニティ/チャンネル対策 | |
title = d.getElementsByTagName('h1')[0].innerHTML; | |
// この処理に改善の余地あり | |
author = d.getElementsByTagName('strong')[3].innerHTML + ' さん'; | |
videoHeader = $('.content_312') | |
} | |
// タグを作成する | |
var el = function(e) { | |
return d.createElement(e); | |
} | |
// アイコンを変える | |
var changeIcon = function(obj, flag) { | |
obj.src = (flag) ? ok : ng; | |
obj.style.borderTop = (flag) ? 'solid 1px #666' : 'solid 1px white'; | |
obj.style.borderLeft = (flag) ? 'solid 1px #666' : 'solid 1px white'; | |
obj.style.borderRight = (flag) ? 'solid 2px #ccc' : 'solid 2px white'; | |
obj.style.borderBottom = (flag) ? 'solid 2px #ccc' : 'solid 2px white'; | |
obj.style.backgroundColor = (flag) ? 'white' : 'white'; | |
}; | |
// 処理 | |
// ブログパーツを削除する | |
var deleteBlogParts = function() { | |
var t = $('#' + blogPartsId); | |
// 既にある場合に削除 | |
if (t) { | |
t.parentNode.removeChild(t); | |
} | |
} | |
// ブログパーツを表示する | |
var showBlogParts = function() { | |
if (!videoId || !title) | |
return; | |
// 既にある場合には削除 | |
deleteBlogParts(); | |
if (bp) { | |
// 外部プレイヤー | |
var extPlayer = '<script type="text/javascript" src="http://ext.nicovideo.jp/thumb_watch/' | |
+ videoId | |
+ '"></script>' | |
+ '<noscript><a href="http://www.nicovideo.jp/watch/' | |
+ videoId | |
+ '">' + title + '</a></noscript>'; | |
// 動画情報 | |
var movieInfo = '<iframe width="312" height="176" src="http://ext.nicovideo.jp/thumb/' | |
+ videoId | |
+ '" scrolling="no" style="border:solid 1px #CCC;" frameborder="0"><a href="http://www.nicovideo.jp/watch/' | |
+ videoId + '">' + title + '</a></iframe>'; | |
// 表示するテキストを作成 | |
var asterisk = (vt) ? '**' + htmlEscape(title) + '(' + htmlEscape(author) | |
+ ')' + "\n" : ''; | |
var str = '<table cellspacing="0">' | |
+ '<tr><th class="font10">外部プレイヤー</th></tr>' | |
+ '<tr><td><form name="form_script"><input type="text" style="width: 278px;" readonly="true" name="script_code" onclick="javascript:document.form_script.script_code.focus(); document.form_script.script_code.select();" value="' | |
+ htmlEscape(extPlayer) | |
+ '"></form></td></tr>' | |
+ '<tr><th class="font10">動画情報</th></tr>' | |
+ '<tr><td><form name="form_iframe"><textarea style="width: 278px;" readonly="true" name="iframe_code" onclick="javascript:document.form_iframe.iframe_code.focus(); document.form_iframe.iframe_code.select();">' | |
+ asterisk + htmlEscape(movieInfo) + '</textarea></form></td></tr>' | |
+ '</table>'; | |
// 表示 | |
var d = el('div'); | |
d.id = blogPartsId; | |
d.style.padding = "10px"; | |
d.style.color = "#000"; | |
d.style.backgroundColor = "#ccc"; | |
d.style.fontSize = "x-small"; | |
d.style.width = "288px"; | |
if (isLogined) { | |
d.style.position = "absolute"; | |
if (isHtml5) { | |
d.style.right = "-250px"; | |
d.style.bottom = "44px"; | |
} else { | |
d.style.right = "-318px"; | |
d.style.bottom = "60px"; | |
} | |
} else { | |
d.style.marginBottom = "20px"; | |
} | |
d.style.opacity = "0.5"; | |
d.style.zIndex = "802"; | |
d.innerHTML = str; | |
d.addEventListener("mouseover", function() { | |
if (isLogined) { | |
if (isHtml5) { | |
d.style.right = "-200px"; | |
} else { | |
d.style.right = "-268px"; | |
} | |
} | |
d.style.opacity = "1"; | |
}, false); | |
d.addEventListener("mouseout", function() { | |
if (isHtml5) { | |
d.style.right = "-250px"; | |
} else { | |
d.style.right = "-318px"; | |
} | |
d.style.opacity = "0.5"; | |
}, false); | |
ins(d, videoHeader); | |
} | |
} | |
// コントロールチップを削除する | |
var deleteControlTip = function() { | |
var t = $('#' + tipId); | |
// 既にある場合に削除 | |
if (t) { | |
t.parentNode.removeChild(t); | |
} | |
} | |
// コントロールチップを表示する | |
var createControlTip = function() { | |
deleteControlTip(); | |
var div = el('div'); | |
div.id = tipId; | |
div.style.color = "#000"; | |
div.style.backgroundColor = "#ccc"; | |
div.style.fontSize = "x-small"; | |
div.style.position = "absolute"; | |
div.style.opacity = "0.5"; | |
div.style.width = "260px"; | |
div.style.padding = "10px"; | |
if (isHtml5) { | |
div.style.right = "-222px"; | |
div.style.bottom = "0px"; | |
} else { | |
div.style.right = "-290px"; | |
div.style.bottom = "12px"; | |
} | |
div.style.zIndex = "802"; | |
div.addEventListener("mouseover", function() { | |
if (isHtml5) { | |
div.style.right = "-172px"; | |
} else { | |
div.style.right = "-240px"; | |
} | |
div.style.opacity = "1"; | |
}, false); | |
d.addEventListener("mouseout", function() { | |
if (isHtml5) { | |
div.style.right = "-222px"; | |
} else { | |
div.style.right = "-290px"; | |
} | |
div.style.opacity = "0.5"; | |
}, false); | |
// button setting | |
var btn = el('img'); | |
btn.style.cursor = 'pointer'; | |
btn.style.padding = "5px"; | |
btn.style.margin = "0 2px 0 0"; | |
// button1: showBlogParts | |
var btn1 = btn.cloneNode(true); | |
btn1.title = 'ブログパーツを表示する'; | |
changeIcon(btn1, bp); | |
btn1.addEventListener('click', function(e) { | |
if (bp) { | |
bp = 0; | |
} else { | |
bp = 1; | |
} | |
showBlogParts(); | |
changeIcon(btn1, bp); | |
GM_setValue('showBlogParts', bp); | |
}, false); | |
div.appendChild(btn1); | |
// button2: showVideoTitleOnBlogParts | |
var btn2 = btn.cloneNode(true); | |
btn2.title = 'ブログパーツに動画タイトルを表示する'; | |
changeIcon(btn2, vt); | |
btn2.addEventListener('click', function(e) { | |
if (vt) { | |
vt = 0; | |
} else { | |
vt = 1; | |
} | |
showBlogParts(); | |
changeIcon(btn2, vt); | |
GM_setValue('showVideoTitleOnBlogParts', vt); | |
}, false); | |
div.appendChild(btn2); | |
// 表示 | |
videoHeader.appendChild(div); | |
} | |
// fire | |
var fire = function() { | |
if (isLogined) { | |
initVideoData(); | |
createControlTip(); | |
} else { | |
initVideoDataNotLogined(); | |
} | |
showBlogParts(); | |
} | |
// コントロール | |
// Greasemonkeyで取得出来るページ情報は初回ページ読み込み時しか更新されないので | |
// タイトルの更新をListenして把握する | |
// 問題はDOMNodeInsertedが同期なこととdeprecatedなこと | |
// ちなみにそれでもwatchAPIDataContainerの取得は捕捉出来ないので利用を断念 | |
// ログイン済みはh2未ログインはh1 | |
var timer = 0; | |
var titleArea = (isLogined && !isHtml5) ? d.getElementsByTagName('h2')[0] : d | |
.getElementsByTagName('h1')[0]; | |
titleArea.addEventListener('DOMNodeInserted', function() { | |
if (timer) | |
return; | |
timer = setTimeout(function() { | |
if (titleArea.innerHTML) { | |
// 動画読み込み時の処理 | |
fire(); | |
timer = 0; | |
return; | |
} | |
timer = 0; | |
}, 30); | |
}, false); | |
// 動画視聴ページを直接開いたときの処理 | |
fire(); | |
})(); |