Nico Auto Play

Auto play mylist.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name           Nico Auto Play
// @description:en Auto play mylist.
// @description:ja ニコニコ動画のシリーズ物動画を連続再生するスクリプトです。
// @version        1.0
// @namespace      http://twitter.com/foldrr/
// @include        http://www.nicovideo.jp/watch/*
// @description Auto play mylist.
// ==/UserScript==
(function(){
	/**
	 * デバッグモード。
	 */
	var DEBUG = 0;
	
	/**
	 * スクリプト名。
	 */
	var APP_NAME = "nico_auto";
	
	/**
	 * 常に動画を自動再生するか?
	 */
	var AUTOPLAY_ALWAYS = false;
	
	/**
	 * 動画の自動再生を制御するフラグ。
	 * AUTOPLAY_ALWAYSがオフの場合はこのフラグを見て自動再生するか決定する。
	 */
	var AUTOPLAY_FLAG = "a=1";
	
	/**
	 * 次回分の動画が複数存在する場合でも自動遷移を試みる。
	 */
	var AUTOPLAY_FORCE = true;
	
	/**
	 * タイマーのインターバル。
	 */
	var INTERVAL = 2 * 1000;
	
	/**
	 * 動画へのリンクを探すためのXPATH文字列。
	 */
	var XPATH = '//div[@id="WATCHHEADER"]//a';
	
	/**
	 * 動画へのリンクだけを抜き出すためのプレフィックス。
	 */
	var HREF_TO_WATCH = "http://www.nicovideo.jp/watch/";
	
	/**
	 * プレイヤーの名前
	 */
	var PLAYER_NAME = "flvplayer";
	
	/**
	 * プレイヤーの状態(ニコニコ動画の仕様変更により変わる可能性あり)
	 */
	var STATUS_STOPPED = "stopped";
	var STATUS_LOAD    = "load";
	var STATUS_PAUSED  = "paused";
	var STATUS_PLAYING = "playing";
	var STATUS_END     = "end";
	
	/**
	 * ニコニコ動画プレイヤーのオブジェクト
	 */
	// var player = unsafeWindow.$(PLAYER_NAME);
	var player = document.getElementById(PLAYER_NAME).wrappedJSObject;
	
	/**
	 * ニコニコ動画プレイヤーの再生状態
	 */
	var currStatus;  // 現在の状態。
	var lastStatus;  // 直前の状態。
	
	/**
	 * メイン処理
	 */
	(function(){
		// プレイヤーの再生状態を更新する。
		refreshStatus();
		trace("再生状態: " + lastStatus + " => " + currStatus);
		
		// プレイヤーの再生状態が準備完了になっていれば自動再生を開始する。
		if(isReady()){
			trace("再生準備完了");
			if(isAutoPlay(location.href)){
				playMovie();
			}
		}
		
		// プレイヤーの再生状態が終了した瞬間以外なら処理を始めからやり直す。
		if(! hasEnded()){
			setTimeout(arguments.callee, INTERVAL);
			return;
		}
		trace("再生終了");
		
		// 動画へのリンクを取得。
		var linksToVideo = [];
		var links = $X(XPATH, document);
		for(var i = 0, n = links.snapshotLength; i < n; i++){
			var href = links.snapshotItem(i).href;
			if(href.indexOf(HREF_TO_WATCH) == 0){
				if(location.href < href){
					linksToVideo.push(href);
				}
			}
		}
		trace("次回動画候補件数: " + linksToVideo.length + "件");
		
		// 次回分の動画が存在しない場合。
		var len = linksToVideo.length;
		if(len === 0){
			return;
		}
		
		// 次回分の動画が1つだけ存在した場合。
		if(len == 1){
			var linkToNext = linksToVideo[linksToVideo.length - 1];
			if(linkToNext){
				location.href = linkToNext + "?" + AUTOPLAY_FLAG;
			}
			return;
		}
		
		// 次回分の動画が複数存在する場合。
		if(2 <= len){
			// 次回分の動画をユーザーが選択するよう促して終了する。
			if(! AUTOPLAY_FORCE){
				var message = document.createElement("div");
				message.innerHTML = "該当する次の動画が複数あるため自動ジャンプを中断します。";
				message.style.borderColor = "#FF0000";
				message.style.borderStyle = "solid";
				message.style.borderWidth = "2px";
				message.style.padding = "10px";
				message.style.backgroundColor = "#FFDDDD";
				message.style.fontSize = "20pt";
				message.style.fontWeight = "bold";
				player.parentNode.insertBefore(message, player);
				return;
			}
			
			// 次回動画を自動選択するために情報を取得する。
			var infos = [];
			for(var i = 0, n = linksToVideo.length; i < n; i++){
				var link = linksToVideo[i];
				var slash = link.lastIndexOf('/');
				var id = link.slice(slash + 1, link.length);
				GM_xmlhttpRequest({
					method: "GET",
					url: "http://ext.nicovideo.jp/api/getthumbinfo/" + id,
					onload: function(x){
						var parser = new DOMParser();
						var dom = parser.parseFromString(x.responseText, "application/xml");
						infos.push(dom);
					}
				});
			}
			
			// 次回動画へ自動遷移する。
			(function(){
				// 全てのタイトルを取得し終わるまで待つ。
				if(infos.length < linksToVideo.length){
					trace("次回動画の情報を取得中..." +
						infos.length + " / " + linksToVideo.length);
					setTimeout(arguments.callee, 200);
					return;
				}
				
				// 全ての次回動画から現在のタイトルに最も似ている動画を探す。
				var title = document.title;
				var nextIndex = null;
				var sameLength = 0;
				for(var i = 0, n = infos.length; i < n; i++){
					var nextTitle = $XSTR("//title", infos[i]);
					var len = equality(title, nextTitle);
					if(sameLength < len){
						nextIndex = i;
						sameLength = len;
					}
					
					if(nextIndex !== null){
						trace("次回動画候補: " + $XSTR("//title", infos[nextIndex]));
					}
				}
				
				// 最も似ている動画へ自動遷移する。
				if(nextIndex){
					var id = $XSTR("//video_id", infos[nextIndex]);
					location.href = "http://www.nicovideo.jp/watch/" + id + "?" + AUTOPLAY_FLAG;
				}
			})();
		}
		
		/**
		 * プレイヤーの再生が終了した瞬間か?
		 */
		function hasEnded(){
			return lastStatus === STATUS_PLAYING && currStatus === STATUS_END;
		}
		
		/**
		 * 自動再生するか?
		 */
		function isAutoPlay(href){
			return AUTOPLAY_ALWAYS || (0 <= href.indexOf(AUTOPLAY_FLAG));
		}
		
		/**
		 * プレイヤーの準備が完了したか?
		 */
		function isReady(){
			return lastStatus === STATUS_STOPPED &&
			       currStatus === STATUS_PAUSED;
		}
		
		// プレイヤーの再生を開始する。
		function playMovie(){
			try {
				if(player.ext_play){
					player.ext_play(1);
				}
			}
			catch(e){
			}
		}
		
		// プレイヤーの再生状態を最新にする。
		function refreshStatus(){
			// ext_getStatus() でコケる時があるので。
			try{
				lastStatus = currStatus;
				currStatus = player.ext_getStatus();
			}
			catch(e){
			}
		}
	})();
	
	/**
	 * ユーティリティ: XPATHから要素を取得する。
	 */
	function $X(xpath, node){
		var nodes = node.evaluate(xpath,
			node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
		return nodes;
	}
	
	/**
	 * ユーティリティ: XPATHから要素の文字列を取得する。
	 */
	function $XSTR(xpath, node){
		var r = node.evaluate(xpath,
			node, null, XPathResult.STRING_TYPE, null);
		return r.stringValue;
	}
	
	/**
	 * ユーティリティ: 文字列が先頭から何文字一致するか調べる。
	 */
	function equality(left, right){
		var n = left.length < right.length ? left.length : right.length;
		for(var i = 0; i < n; i++){
			if(left.charAt(i) !== right.charAt(i)){
				break;
			}
		}
		
		return i;
	}
	
	/**
	 * ユーティリティ: デバッグメッセージを出力する。
	 */
	function trace(s){
		if(DEBUG){
			console.log(APP_NAME + ": " + s);
		}
	}
})();