您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
シークバーに出るサムネイルを並べて表示
当前为
// ==UserScript== // @name NicovideoStoryboard // @namespace https://github.com/segabito/ // @description シークバーに出るサムネイルを並べて表示 // @match http://www.nicovideo.jp/watch/* // @match http://flapi.nicovideo.jp/api/getflv* // @match http://*.nicovideo.jp/smile* // @grant none // @author segabito macmoto // @version 1.0.2 // ==/UserScript== // ver 1.0.1 フルスクリーンモードで開いた時はプレイヤー領域を押し上げるようにした (function() { var monkey = function() { var DEBUG = !true; var $ = window.jQuery, _ = window._; var __css__ = (function() {/* .xDomainLoaderFrame { position: fixed; top: -999px; left: -999px; width: 1px; height: 1px; border: 0; } .storyboardContainer { position: fixed; bottom: -300px; left: 0; right: 0; width: 100%; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; background: #999; border: 2px outset #000; z-index: 9005; overflow: visible; border-radius: 10px 10px 0 0; border-width: 2px 2px 0; box-shadow: 0 -2px 2px #666; transition: bottom 0.5s ease-in-out; } .storyboardContainer.show { bottom: 0; } .storyboardContainer .storyboardInner { display: none; position: relative; text-align: center; overflow-x: scroll; white-space: nowrap; background: #222; margin: 4px 12px; border-style: inset; border-width: 2px 4px; border-radius: 10px 10px 0 0; } .storyboardContainer.success .storyboardInner { display: block; } .storyboardContainer .storyboardInner .boardList { overflow: hidden; } .storyboardContainer .boardList .board { display: inline-block; cursor: pointer; background-color: #101010; } .storyboardContainer.clicked .storyboardInner .boardList .board { cursor: wait; opacity: 0.5; } .storyboardContainer .boardList .board.lazyImage { background-color: #ccc; } .storyboardContainer .boardList .board.loadFail { background-color: #c99; } .storyboardContainer .boardList .board.lazyImage { cursor: wait; } .storyboardContainer .boardList .board > div { white-space: nowrap; } .storyboardContainer .boardList .board .border { box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; border-style: solid; border-color: #000 #333 #000 #999; border-width: 0 2px 0 2px; display: inline-block; } .storyboardContainer .boardList .board:hover .border { display: none; {* クリックできなくなっちゃうので苦し紛れの対策 もうちょっとマシな方法を考える *} } .storyboardContainer .storyboardHeader { position: relative; width: 100%; } .storyboardContainer .pointer { position: absolute; bottom: -15px; left: 50%; width: 32px; margin-left: -16px; color: #333; z-index: 9010; text-align: center; } .storyboardContainer .cursorTime { display: none; position: absolute; bottom: -30px; left: -999px; font-size: 10pt; border: 1px solid #000; z-index: 9010; background: #ffc; } .storyboardContainer:hover .cursorTime { display: block; } .storyboardContainer .setToDisable { position: absolute; display: inline-block; left: 250px; bottom: -32px; transition: bottom 0.3s ease-in-out; } .storyboardContainer:hover .setToDisable { bottom: 0; } .storyboardContainer .setToDisable button, .setToEnableButtonContainer button { background: none repeat scroll 0 0 #999; border-color: #666; border-radius: 18px 18px 0 0; border-style: solid; border-width: 2px 2px 0; width: 200px; overflow: auto; white-space: nowrap; cursor: pointer; box-shadow: 0 -2px 2px #666; } .full_with_browser .setToEnableButtonContainer button { box-shadow: none; color: #888; background: #000; } .full_with_browser .storyboardContainer .setToDisable, .full_with_browser .setToEnableButtonContainer { background: #000; {* Firefox対策 *} } .setToEnableButtonContainer button { width: 200px; } .storyboardContainer .setToDisable button:hover, .setToEnableButtonContainer:not(.loading):not(.fail) button:hover { background: #ccc; transition: none; } .storyboardContainer .setToDisable button.clicked, .setToEnableButtonContainer.loading button, .setToEnableButtonContainer.fail button, .setToEnableButtonContainer button.clicked { border-style: inset; box-shadow: none; } .setToEnableButtonContainer { position: fixed; z-index: 9003; left: 250px; bottom: 0px; transition: bottom 0.5s ease-in-out; } .setToEnableButtonContainer.loadingVideo { bottom: -50px; } .setToEnableButtonContainer.loading *, .setToEnableButtonContainer.loadingVideo *{ cursor: wait; font-size: 80%; } .setToEnableButtonContainer.fail { color: #999; cursor: default; font-size: 80%; } */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/'); var storyboardTemplate = [ '<div id="storyboardContainer" class="storyboardContainer">', '<div class="storyboardHeader">', '<div class="setToDisable"><button>閉じる ▼</button></div>', '<div class="pointer">▼</div>', '<div class="cursorTime"></div>', '</div>', '<div class="storyboardInner"></div>', '<div class="failMessage">', '</div>', '</div>', '', ''].join(''); // マスコットキャラクターのサムネーヨちゃん var noThumbnailAA = (function() {/* ∧ ∧ ┌───────────── ( ´ー`) < サムネーヨ \ < └───/|──────── \.\______// \ / ∪∪‾∪∪ */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/'); var EventDispatcher = (function() { function AsyncEventDispatcher() { window.WatchApp.extend( this, AsyncEventDispatcher, window.WatchApp.ns.event.EventDispatcher); } window.WatchApp.mixin(AsyncEventDispatcher.prototype, { // addEventListener: function(name, func) { // console.log('%caddEventListener: ', 'background: red; color: white;', name, func); // if (!func) { // console.trace(); // } // AsyncEventDispatcher.__super__.addEventListener.call(this, name, func); // }, dispatchAsync: function() { var args = arguments; window.setTimeout($.proxy(function() { try { this.dispatchEvent.apply(this, args); } catch (e) { console.log(e); } }, this), 0); } }); return AsyncEventDispatcher; })(); window.NicovideoStoryboard = (function() { return { api: {}, model: {}, view: {}, controller: {}, external: {}, event: {} }; })(); // var console = // (function(debug) { // var n = window._.noop; // return debug ? window.console : {log: n, trace: n, time: n, timeEnd: n}; // })(DEBUG); var console = window.console; window.NicovideoStoryboard.event.windowEventDispatcher = (function() { var eventDispatcher = new EventDispatcher(); var onMessage = function(event) { if (event.origin.indexOf('nicovideo.jp') < 0) return; try { var data = JSON.parse(event.data); if (data.id !== 'NicovideoStoryboard') { return; } eventDispatcher.dispatchEvent('onMessage', data.body, data.type); } catch (e) { console.log( '%cNicoVideoStoryboard.Error: window.onMessage - ', 'color: red; background: yellow', e ); console.log('%corigin: ', 'background: yellow;', event.origin); console.log('%cdata: ', 'background: yellow;', event.data); console.trace(); } }; window.addEventListener('message', onMessage); return eventDispatcher; })(); window.NicovideoStoryboard.external.watchController = (function() { var root = window.WatchApp.ns; var nicoPlayerConnector = root.init.PlayerInitializer.nicoPlayerConnector; var watchInfoModel = root.model.WatchInfoModel.getInstance(); var viewerInfoModel = root.init.CommonModelInitializer.viewerInfoModel; var playerAreaConnector = root.init.PlayerInitializer.playerAreaConnector; var externalNicoplayer; var watchController = new EventDispatcher(); var getVpos = function() { return nicoPlayerConnector.getVpos(); }; var setVpos = function(vpos) { nicoPlayerConnector.seekVideo(vpos); }; var _isPlaying = null; var isPlaying = function() { if (_isPlaying !== null) { return _isPlaying; } if (!externalNicoplayer) { externalNicoplayer = $("#external_nicoplayer")[0]; } var status = externalNicoplayer.ext_getStatus(); return status === 'playing'; }; var play = function() { nicoPlayerConnector.playVideo(); }; var pause = function() { nicoPlayerConnector.stopVideo(); }; var isPremium = function() { return !!viewerInfoModel.isPremium; }; var getWatchId = function() {// スレッドIDだったりsmXXXXだったり return watchInfoModel.v; }; var getVideoId = function() {// smXXXXXX, soXXXXX など return watchInfoModel.id; }; var popupMarquee = root.init.PopupMarqueeInitializer.popupMarqueeViewController; var popup = { message: function(text) { popupMarquee.onData( '<span style="background: black;">' + text + '</span>' ); }, alert: function(text) { popupMarquee.onData( '<span style="background: black; color: red;">' + text + '</span>' ); } }; playerAreaConnector.addEventListener('onVideoPlayed', function() { _isPlaying = true; watchController.dispatchEvent('onVideoPlayed'); }); playerAreaConnector.addEventListener('onVideoStopped', function() { _isPlaying = false; watchController.dispatchEvent('onVideoStopped'); }); playerAreaConnector.addEventListener('onVideoStarted', function() { _isPlaying = true; watchController.dispatchEvent('onVideoStarted'); }); playerAreaConnector.addEventListener('onVideoEnded', function() { _isPlaying = false; watchController.dispatchEvent('onVideoEnded'); }); playerAreaConnector.addEventListener('onVideoSeeked', function() { watchController.dispatchEvent('onVideoSeeked'); }); window.WatchApp.mixin(watchController, { getVpos: getVpos, setVpos: setVpos, isPlaying: isPlaying, play: play, pause: pause, isPremium: isPremium, getWatchId: getWatchId, getVideoId: getVideoId, popup: popup }); return watchController; })(); window.NicovideoStoryboard.api.getflv = (function() { var BASE_URL = 'http://flapi.nicovideo.jp/api/getflv?v='; var loaderFrame, loaderWindow, cache = {}; var eventDispatcher = window.NicovideoStoryboard.event.windowEventDispatcher; var getflv = new EventDispatcher(); var parseInfo = function(q) { var info = {}, lines = q.split('&'); $.each(lines, function(i, line) { var tmp = line.split('='); var key = window.unescape(tmp[0]), value = window.unescape(tmp[1]); info[key] = value; }); return info; }; var onMessage = function(data, type) { if (type !== 'getflv') { return; } var info = parseInfo(data.info), url = data.url; cache[url] = info; //console.log('getflv.onGetflvLoad', info); getflv.dispatchAsync('onGetflvLoad', info); }; var initialize = function() { initialize = _.noop; console.log('%c initialize getflv', 'background: lightgreen;'); loaderFrame = document.createElement('iframe'); loaderFrame.name = 'getflvLoader'; loaderFrame.className = DEBUG ? 'xDomainLoaderFrame debug' : 'xDomainLoaderFrame'; document.body.appendChild(loaderFrame); loaderWindow = loaderFrame.contentWindow; eventDispatcher.addEventListener('onMessage', onMessage); }; var load = function(watchId) { initialize(); var url = BASE_URL + watchId; //console.log('getflv: ', url); getflv.dispatchEvent('onGetflvLoadStart', watchId); if (cache[url]) { //console.log('%cgetflv cache exist', 'background: cyan', url); getflv.dispatchAsync('onGetflvLoad', cache[url]); } else { loaderWindow.location.replace(url); } }; window.WatchApp.mixin(getflv, { load: load }); return getflv; })(); window.NicovideoStoryboard.api.thumbnailInfo = (function() { var getflv = window.NicovideoStoryboard.api.getflv; var loaderFrame, loaderWindow, cache = {}; var eventDispatcher = window.NicovideoStoryboard.event.windowEventDispatcher; var thumbnailInfo = new EventDispatcher(); var onGetflvLoad = function(info) { //console.log('thumbnailInfo.onGetflvLoad', info); if (!info.url) { thumbnailInfo.dispatchAsync( 'onThumbnailInfoLoad', {status: 'ng', message: 'サムネイル情報の取得に失敗しました'} ); return; } else if (info.url.indexOf('http://') !== 0) { // rtmpe:~など thumbnailInfo.dispatchAsync( 'onThumbnailInfoLoad', {status: 'ng', message: 'この配信形式には対応していません'} ); return; } var url = info.url + '&sb=1'; thumbnailInfo.dispatchEvent('onThumbnailInfoLoadStart'); if (cache[url]) { //console.log('%cthumbnailInfo cache exist', 'background: cyan', url); thumbnailInfo.dispatchAsync('onThumbnailInfoLoad', cache[url]); return; } loaderWindow.location.replace(url); }; var onMessage = function(data, type) { if (type !== 'storyboard') { return; } //console.log('thumbnailInfo.onMessage: ', data, type); var url = data.url; var xml = data.xml, $xml = $(xml), $storyboard = $xml.find('storyboard'); if ($storyboard.length < 1) { thumbnailInfo.dispatchAsync( 'onThumbnailInfoLoad', {status: 'ng', message: 'この動画にはサムネイルがありません'} ); return; } var info = { status: 'ok', message: '成功', url: data.url, movieId: $xml.find('movie').attr('id'), duration: $xml.find('duration').text(), thumbnail:{ width: $storyboard.find('thumbnail_width').text(), height: $storyboard.find('thumbnail_height').text(), number: $storyboard.find('thumbnail_number').text(), interval: $storyboard.find('thumbnail_interval').text() }, board: { rows: $storyboard.find('board_rows').text(), cols: $storyboard.find('board_cols').text(), number: $storyboard.find('board_number').text() } }; cache[url] = info; thumbnailInfo.dispatchAsync('onThumbnailInfoLoad', info); }; var initialize = function() { initialize = _.noop; console.log('%c initialize thumbnailInfo', 'background: lightgreen;'); loaderFrame = document.createElement('iframe'); loaderFrame.name = 'StoryboardLoader'; loaderFrame.className = DEBUG ? 'xDomainLoaderFrame debug' : 'xDomainLoaderFrame'; document.body.appendChild(loaderFrame); loaderWindow = loaderFrame.contentWindow; eventDispatcher.addEventListener('onMessage', onMessage); getflv.addEventListener('onGetflvLoad', onGetflvLoad); }; var load = function(watchId) { initialize(); getflv.load(watchId); }; window.WatchApp.mixin(thumbnailInfo, { load: load }); return thumbnailInfo; })(); window.NicovideoStoryboard.model.StoryboardModel = (function() { function StoryboardModel(params) { this._thumbnailInfo = params.thumbnailInfo; this._isEnabled = params.isEnabled; this._watchId = params.watchId; window.WatchApp.extend(this, StoryboardModel, EventDispatcher); } window.WatchApp.mixin(StoryboardModel.prototype, { initialize: function(info) { console.log('%c initialize StoryboardModel', 'background: lightgreen;'); this.update(info); }, update: function(info) { if (info.status !== 'ok') { window.WatchApp.mixin(info, { url: '', width: 1, height: 1, duration: 1, thumbnail: { width: 1, height: 1, number: 1, interval: 1 }, board: { rows: 1, cols: 1 } }); } this._info = info; this.dispatchEvent('update'); }, reset: function() { if (this.isEnabled()) { window.setTimeout($.proxy(function() { this.load(); }, this), 1000); } this.dispatchEvent('reset'); }, load: function() { this._isEnabled = true; this._thumbnailInfo.load(this._watchId); }, setWatchId: function(watchId) { this._watchId = watchId; }, unload: function() { this._isEnabled = false; this.dispatchEvent('unload'); }, isEnabled: function() { return this._isEnabled; }, getStatus: function() { return this._info.status; }, getMessage: function() { return this._info.message; }, getUrl: function() { return this._info.url; }, getDuration: function() { return parseInt(this._info.duration, 10); }, getWidth: function() { return parseInt(this._info.thumbnail.width, 10); }, getHeight: function() { return parseInt(this._info.thumbnail.height, 10); }, getInterval: function() { return parseInt(this._info.thumbnail.interval, 10); }, getCount: function() { return Math.max( Math.ceil(this.getDuration() / Math.max(0.01, this.getInterval())), parseInt(this._info.thumbnail.number, 10) ); }, getRows: function() { return parseInt(this._info.board.rows, 10); }, getCols: function() { return parseInt(this._info.board.cols, 10); }, getPageCount: function() { return parseInt(this._info.board.number, 10); }, getTotalRows: function() { return Math.ceil(this.getCount() / this.getCols()); }, getPageWidth: function() { return this.getWidth() * this.getCols(); }, getPageHeight: function() { return this.getHeight() * this.getRows(); }, getCountPerPage: function() { return this.getRows() * this.getCols(); }, /** * nページ目のURLを返す。 ゼロオリジン */ getPageUrl: function(page) { page = Math.max(0, Math.min(this.getPageCount() - 1, page)); return this.getUrl() + '&board=' + (page + 1); }, /** * vposに相当するサムネは何番目か?を返す */ getIndex: function(vpos) { // msec -> sec var v = Math.floor(vpos / 1000); v = Math.max(0, Math.min(this.getDuration(), v)); // サムネの総数 ÷ 秒数 // Math.maxはゼロ除算対策 var n = this.getCount() / Math.max(1, this.getDuration()); return parseInt(Math.floor(v * n), 10); }, /** * Indexのサムネイルは何番目のページにあるか?を返す */ getPageIndex: function(thumbnailIndex) { var perPage = this.getCountPerPage(); var pageIndex = parseInt(thumbnailIndex / perPage, 10); return Math.max(0, Math.min(this.getPageCount(), pageIndex)); }, /** * vposに相当するサムネは何ページの何番目にあるか?を返す */ getThumbnailPosition: function(vpos) { var thumbnailIndex = this.getIndex(vpos); var pageIndex = this.getPageIndex(thumbnailIndex); var mod = thumbnailIndex % this.getCountPerPage(); var row = Math.floor(mod / Math.max(1, this.getCols())); var col = mod % this.getRows(); return { page: pageIndex, index: thumbnailIndex, row: row, col: col }; }, /** * nページ目のx, y座標をvposに変換して返す */ getPointVpos: function(x, y, page) { var width = Math.max(1, this.getWidth()); var height = Math.max(1, this.getHeight()); var row = Math.floor(y / height); var col = Math.floor(x / width); var mod = x % width; // 何番目のサムネに相当するか? var point = page * this.getCountPerPage() + row * this.getCols() + col + (mod / width) // 小数点以下は、n番目の左端から何%あたりか ; // 全体の何%あたり? var percent = point / Math.max(1, this.getCount()); percent = Math.max(0, Math.min(100, percent)); // vposは㍉秒単位なので1000倍 return Math.floor(this.getDuration() * percent * 1000); }, /** * vposは何ページ目に当たるか?を返す */ getVposPage: function(vpos) { var index = this._storyboard.getIndex(vpos); var page = this._storyboard.getPageIndex(index); return page; } }); return StoryboardModel; })(); window.NicovideoStoryboard.view.FullScreenModeView = (function() { var __TEMPLATE__ = (function() {/* body.full_with_browser{ background: #000; } body.full_with_browser.NicovideoStoryboardOpen #content{ margin-bottom: {$storyboardHeight}px; transition: margin-bottom 0.5s ease-in-out; } */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/'); var addStyle = function(styles, id) { var elm = document.createElement('style'); window.setTimeout(function() { elm.type = 'text/css'; if (id) { elm.id = id; } var text = styles.toString(); text = document.createTextNode(text); elm.appendChild(text); var head = document.getElementsByTagName('head'); head = head[0]; head.appendChild(elm); }, 0); return elm; }; function FullScreenModeView() { this._css = null; this._lastHeight = -1; } window.WatchApp.mixin(FullScreenModeView.prototype, { initialize: function() { if (this._css) { return; } console.log('%cinitialize NicovideoStorybaordFullScreenStyle', 'background: lightgreen;'); this._css = addStyle('/* undefined */', 'NicovideoStorybaordFullScreenStyle'); }, update: function($container) { this.initialize(); var height = $container.outerHeight(); if (height === this._lastHeight) { return; } this._lastHeight = height; var newCss = __TEMPLATE__.replace('{$storyboardHeight}', height); this._css.innerHTML = newCss; } }); return FullScreenModeView; })(); window.NicovideoStoryboard.view.SetToEnableButtonView = (function() { var TEXT = { DEFAULT: 'サムネイルを開く ▲', LOADING: '動画を読み込み中...', GETFLV: '動画情報を読み込み中...', THUMBNAIL: 'サムネイル情報を読み込み中...' }; function SetToEnableButtonView(params) { this._storyboard = params.storyboard; this._eventDispatcher = params.eventDispatcher; this.initialize(); } window.WatchApp.mixin(SetToEnableButtonView.prototype, { initialize: function() { console.log('%cinitialize SetToEnableButtonView', 'background: lightgreen;'); this._$view = $([ '<div class="setToEnableButtonContainer loadingVideo">', '<button>', TEXT.DEFAULT, '</button>', '</div>', '', '', ''].join('')); this._$button = this._$view.find('button'); this._$button.on('click', $.proxy(this._onButtonClick, this)); var sb = this._storyboard; sb.addEventListener('reset', $.proxy(this._onStoryboardReset, this)); sb.addEventListener('update', $.proxy(this._onStoryboardUpdate, this)); var evt = this._eventDispatcher; evt.addEventListener('getFlvLoadStart', $.proxy(this._onGetflvLoadStart, this)); evt.addEventListener('onThumbnailInfoLoadStart', $.proxy(this._onThumbnailInfoLoadStart, this)); evt.addEventListener('onWatchInfoReset', $.proxy(this._onWatchInfoReset, this)); $('body').append(this._$view); }, reset: function() { this._$view.attr('title', ''); if (this._storyboard.isEnabled()) { this._$view.removeClass('loadingVideo getflv thumbnailInfo fail success').addClass('loading'); this._setText(TEXT.GETFLV); } else { this._$view.removeClass('loadingVideo getflv thumbnailInfo fail success loading'); this._setText(TEXT.DEFAULT); } }, _setText: function(text) { this._$button.text(text); }, _onButtonClick: function(e) { if ( this._$view.hasClass('loading') || this._$view.hasClass('loadingVideo') || this._$view.hasClass('fail')) { return; } e.preventDefault(); e.stopPropagation(); var $view = this._$view.addClass('loading clicked'); this._eventDispatcher.dispatchEvent('onEnableStoryboard'); window.setTimeout(function() { $view.removeClass('clicked'); $view = null; }, 1000); }, _onStoryboardReset: function() { this.reset(); }, _onStoryboardUpdate: function() { var storyboard = this._storyboard; if (storyboard.getStatus() === 'ok') { window.setTimeout($.proxy(function() { this._$view .removeClass('loading getflv thumbnailInfo') .addClass('success') .attr('title', ''); this._setText(TEXT.DEFAULT); }, this), 3000); } else { this._$view .removeClass('loading') .addClass('fail') .attr('title', DEBUG ? noThumbnailAA : ''); this._setText(storyboard.getMessage()); } }, _onGetflvLoadStart: function() { this._$view.addClass('loading getflv'); this._setText(TEXT.GETFLV); }, _onThumbnailInfoLoadStart: function() { this._$view.addClass('loading thumbnailInfo'); this._setText(TEXT.THUMBNAIL); }, _onWatchInfoReset: function() { this._$view.addClass('loadingVideo'); this._setText(TEXT.LOADING); } }); return SetToEnableButtonView; })(); window.NicovideoStoryboard.view.StoryboardView = (function() { var TIMER_INTERVAL = 33; var VPOS_RATE = 10; function StoryboardView(params) { this.initialize(params); } window.WatchApp.mixin(StoryboardView.prototype, { initialize: function(params) { console.log('%c initialize StoryboardView', 'background: lightgreen;'); this._watchController = params.watchController; var evt = this._eventDispatcher = params.eventDispatcher; var sb = this._storyboard = params.storyboard; this._isHover = false; this._currentUrl = ''; this._lazyImage = {}; this._lastPage = -1; this._lastVpos = 0; this._lastGetVpos = 0; this._timerCount = 0; this._scrollLeft = 0; this._enableButtonView = new window.NicovideoStoryboard.view.SetToEnableButtonView({ storyboard: sb, eventDispatcher: this._eventDispatcher }); this._fullScreenModeView = new window.NicovideoStoryboard.view.FullScreenModeView(); evt.addEventListener('onWatchInfoReset', $.proxy(this._onWatchInfoReset, this)); sb.addEventListener('update', $.proxy(this._onStoryboardUpdate, this)); sb.addEventListener('reset', $.proxy(this._onStoryboardReset, this)); sb.addEventListener('unload', $.proxy(this._onStoryboardUnload, this)); }, _initializeStoryboard: function() { this._initializeStoryboard = _.noop; console.log('%cStoryboardView.initializeStoryboard', 'background: lightgreen;'); var $view = this._$view = $(storyboardTemplate); var $inner = this._$inner = $view.find('.storyboardInner'); this._$failMessage = $view.find('.failMessage'); this._$cursorTime = $view.find('.cursorTime'); this._$disableButton = $view.find('.setToDisable button'); var self = this; var onHoverIn = function() { self._isHover = true; }; var onHoverOut = function() { self._isHover = false; }; $view .on('click', '.board', $.proxy(this._onBoardClick, this)) .on('mousemove', '.board', $.proxy(this._onBoardMouseMove, this)) .on('mousewheel', $.proxy(this._onMouseWheel, this)); $inner .hover(onHoverIn, onHoverOut) .on('scroll', _.throttle(function() { self._onScroll(); }, 500)); this._watchController .addEventListener('onVideoSeeked', $.proxy(this._onVideoSeeked, this)); this._$disableButton.on('click', $.proxy(this._onDisableButtonClick, this)); $('body').append($view); }, _onBoardClick: function(e) { var $board = $(e.target), offset = $board.offset(); var y = $board.attr('data-top') * 1; var x = e.pageX - offset.left; var page = $board.attr('data-page'); var vpos = this._storyboard.getPointVpos(x, y, page); if (isNaN(vpos)) { return; } var $view = this._$view; $view.addClass('clicked'); window.setTimeout(function() { $view.removeClass('clicked'); }, 300); this._eventDispatcher.dispatchEvent('onStoryboardSelect', vpos); this._isHover = false; if ($board.hasClass('lazyImage')) { this._lazyLoadImage(page); } }, _onBoardMouseMove: function(e) { var $board = $(e.target), offset = $board.offset(); var y = $board.attr('data-top') * 1; var x = e.pageX - offset.left; var page = $board.attr('data-page'); var vpos = this._storyboard.getPointVpos(x, y, page); if (isNaN(vpos)) { return; } var sec = Math.floor(vpos / 1000); var time = Math.floor(sec / 60) + ':' + ((sec % 60) + 100).toString().substr(1); this._$cursorTime.text(time).css({left: e.pageX}); this._isHover = true; if ($board.hasClass('lazyImage')) { this._lazyLoadImage(page); } }, _onMouseWheel: function(e, delta) { e.preventDefault(); e.stopPropagation(); this._isHover = true; var left = this.scrollLeft(); this.scrollLeft(left - delta * 140); }, _onVideoSeeked: function() { if (!this._storyboard.isEnabled()) { return; } if (this._storyboard.getStatus() !== 'ok') { return; } var vpos = this._watchController.getVpos(); var page = this._storyboard.getVposPage(vpos); this._lazyLoadImage(page); if (this.isHover || !this._watchController.isPlaying()) { this._onVposUpdate(vpos, true); } else { this._onVposUpdate(vpos); } }, update: function() { this.disableTimer(); this._initializeStoryboard(); this._$view.removeClass('show success'); $('body').removeClass('NicovideoStoryboardOpen'); if (this._storyboard.getStatus() === 'ok') { this._updateSuccess(); } else { this._updateFail(); } }, scrollLeft: function(left) { if (left === undefined) { return this._scrollLeft; } else if (left === 0 || Math.abs(this._scrollLeft - left) >= 1) { this._$inner[0].scrollLeft = left; this._scrollLeft = left; } }, _updateSuccess: function() { var url = this._storyboard.getUrl(); if (this._currentUrl === url) { this._$view.addClass('show success'); this.enableTimer(); } else { this._currentUrl = url; this._updateSuccessFull(); } $('body').addClass('NicovideoStoryboardOpen'); }, _updateSuccessFull: function() { var storyboard = this._storyboard; var pages = storyboard.getPageCount(); var pageWidth = storyboard.getPageWidth(); var height = storyboard.getHeight(); var rows = storyboard.getRows(); var $borders = this._createBorders(storyboard.getWidth(), storyboard.getHeight(), storyboard.getCols()); var totalRows = storyboard.getTotalRows(); var rowCnt = 0; var $list = $('<div class="boardList"/>') .css({ width: storyboard.getCount() * storyboard.getWidth(), paddingLeft: '50%', paddingRight: '50%', height: height }); for (var i = 0; i < pages; i++) { var src = storyboard.getPageUrl(i); for (var j = 0; j < rows; j++) { var $img = $('<div class="board"/>') .css({ width: pageWidth, height: height, backgroundPosition: '0 -' + height * j + 'px' }) .attr({ 'data-src': src, 'data-page': i, 'data-top': height * j + height / 2 }) .append($borders.clone()); if (i === 0) { // 1ページ目だけ遅延ロードしない $img.css('background-image', 'url(' + src + ')'); } else { $img.addClass('lazyImage page-' + i); } $list.append($img); rowCnt++; if (rowCnt >= totalRows) { break; } } } this._$innerList = $list; this._$inner.empty().append($list).append(this._$pointer); this._$view.removeClass('fail').addClass('success'); this._fullScreenModeView.update(this._$view); window.setTimeout($.proxy(function() { this._$view.addClass('show'); }, this), 100); this.scrollLeft(0); this.enableTimer(); }, _createBorders: function(width, height, count) { var $border = $('<div class="border"/>').css({ width: width, height: height }); var $div = $('<div />'); for (var i = 0; i < count; i++) { $div.append($border.clone()); } return $div; }, _lazyLoadImage: function(pageNumber) { var className = 'page-' + pageNumber; if (pageNumber < 1 || this._lazyImage[className]) { return; } var src = this._storyboard.getPageUrl(pageNumber); this._lazyImage[className] = src; //console.log('%c set lazyLoadImage', 'background: cyan;', 'page: ' + pageNumber, ' url: ' + src); var load = $.proxy(function() { this._$inner.find('.' + className) .css('background-image', 'url(' + src + ')') .removeClass('lazyImage ' + className); }, this); window.setTimeout(load, 0); //window.setTimeout(load, 1000); }, _updateFail: function() { this._$view.removeClass('success').addClass('fail'); this.disableTimer(); }, clear: function() { if (this._$view) { this._$inner.empty(); } this.disableTimer(); }, _clearTimer: function() { if (this._timer) { window.clearInterval(this._timer); this._timer = null; } }, enableTimer: function() { this._clearTimer(); this._isHover = false; this._timer = window.setInterval($.proxy(this._onTimerInterval, this), TIMER_INTERVAL); }, disableTimer: function() { this._clearTimer(); }, _onTimerInterval: function() { if (this._isHover) { return; } if (!this._storyboard.isEnabled()) { return; } var div = VPOS_RATE; var mod = this._timerCount % div; this._timerCount++; var vpos; if (!this._watchController.isPlaying()) { return; } // getVposが意外に時間を取るので回数を減らす // そもそもコメントパネルがgetVpos叩きまくってるんですがそれは if (mod === 0) { vpos = this._watchController.getVpos(); } else { vpos = this._lastVpos; } this._onVposUpdate(vpos); }, _onVposUpdate: function(vpos, isImmediately) { var storyboard = this._storyboard; var duration = Math.max(1, storyboard.getDuration()); var per = vpos / (duration * 1000); var width = storyboard.getWidth(); var boardWidth = storyboard.getCount() * width; var targetLeft = boardWidth * per + width * 0.4; var currentLeft = this.scrollLeft(); var leftDiff = targetLeft - currentLeft; if (Math.abs(leftDiff) > 5000) { leftDiff = leftDiff * 0.93; // 大きくシークした時 } else { leftDiff = leftDiff / VPOS_RATE; } this._lastVpos = vpos; this.scrollLeft(isImmediately ? targetLeft : (currentLeft + Math.round(leftDiff))); }, _onScroll: function() { var storyboard = this._storyboard; var scrollLeft = this.scrollLeft(); var page = Math.round(scrollLeft / (storyboard.getPageWidth() * storyboard.getRows())); this._lazyLoadImage(Math.min(page, storyboard.getPageCount() - 1)); }, reset: function() { this._lastVpos = -1; this._lastPage = -1; this._currentUrl = ''; this._timerCount = 0; this._scrollLeft = 0; this._lazyImage = {}; if (this._$view) { $('body').removeClass('NicovideoStoryboardOpen'); this._$view.removeClass('show'); this._$inner.empty(); } }, _onDisableButtonClick: function(e) { e.preventDefault(); e.stopPropagation(); var $button = this._$disableButton; $button.addClass('clicked'); window.setTimeout(function() { $button.removeClass('clicked'); }, 1000); this._eventDispatcher.dispatchEvent('onDisableStoryboard'); }, _onStoryboardUpdate: function() { this.update(); }, _onStoryboardReset: function() { }, _onStoryboardUnload: function() { $('body').removeClass('NicovideoStoryboardOpen'); if (this._$view) { this._$view.removeClass('show'); } }, _onWatchInfoReset: function() { this.reset(); } }); return StoryboardView; })(); window.NicovideoStoryboard.controller.StoryboardController = (function() { function StoryboardController(params) { this.initialize(params); } window.WatchApp.mixin(StoryboardController.prototype, { initialize: function(params) { console.log('%c initialize StoryboardController', 'background: lightgreen;'); this._thumbnailInfo = params.thumbnailInfo; this._watchController = params.watchController; this._config = params.config; var evt = this._eventDispatcher = params.eventDispatcher; evt.addEventListener('onVideoInitialized', $.proxy(this._onVideoInitialized, this)); evt.addEventListener('onWatchInfoReset', $.proxy(this._onWatchInfoReset, this)); evt.addEventListener('onStoryboardSelect', $.proxy(this._onStoryboardSelect, this)); evt.addEventListener('onEnableStoryboard', $.proxy(this._onEnableStoryboard, this)); evt.addEventListener('onDisableStoryboard', $.proxy(this._onDisableStoryboard, this)); evt.addEventListener('onGetflvLoadStart', $.proxy(this._onGetflvLoadStart, this)); evt.addEventListener('onThumbnailInfoLoadStart', $.proxy(this._onThumbnailInfoLoadStart, this)); evt.addEventListener('onThumbnailInfoLoad', $.proxy(this._onThumbnailInfoLoad, this)); this._initializeStoryboard(); }, _initializeStoryboard: function() { this._initializeStoryboard = _.noop; if (!this._storyboardModel) { var nsv = window.NicovideoStoryboard; this._storyboardModel = new nsv.model.StoryboardModel({ thumbnailInfo: this._thumbnailInfo, isEnabled: this._config.get('enabled') === true, watchId: this._watchController.getWatchId() }); } if (!this._storyboardView) { this._storyboardView = new window.NicovideoStoryboard.view.StoryboardView({ watchController: this._watchController, eventDispatcher: this._eventDispatcher, storyboard: this._storyboardModel }); } }, load: function(watchId) { if (watchId) { this._storyboardModel.setWatchId(watchId); } this._storyboardModel.load(); }, unload: function() { if (this._storyboardModel) { this._storyboardModel.unload(); } }, _onVideoInitialized: function() { this._initializeStoryboard(); this._storyboardModel.reset(); }, _onWatchInfoReset: function() { this._storyboardModel.setWatchId(this._watchController.getWatchId()); }, _onThumbnailInfoLoad: function(info) { //console.log('StoryboardController._onThumbnailInfoLoad', info); this._storyboardModel.update(info); }, _onStoryboardSelect: function(vpos) { //console.log('_onStoryboardSelect', vpos); this._watchController.setVpos(vpos); }, _onEnableStoryboard: function() { window.setTimeout($.proxy(function() { this._config.set('enabled', true); this.load(); }, this), 0); }, _onDisableStoryboard: function() { window.setTimeout($.proxy(function() { this._config.set('enabled', false); this.unload(); }, this), 0); }, _onGetflvLoadStart: function() { }, _onThumbnailInfoLoadStart: function() { } }); return StoryboardController; })(); window.WatchApp.mixin(window.NicovideoStoryboard, { _addStyle: function(styles, id) { var elm = document.createElement('style'); window.setTimeout(function() { elm.type = 'text/css'; if (id) { elm.id = id; } var text = styles.toString(); text = document.createTextNode(text); elm.appendChild(text); var head = document.getElementsByTagName('head'); head = head[0]; head.appendChild(elm); }, 0); return elm; }, initialize: function() { console.log('%c initialize NicovideoStoryboard', 'background: lightgreen;'); this.initializeUserConfig(); var root = window.WatchApp.ns; this._playerAreaConnector = root.init.PlayerInitializer.playerAreaConnector; this._watchInfoModel = root.model.WatchInfoModel.getInstance(); this._getflv = window.NicovideoStoryboard.api.getflv; this._thumbnailInfo = window.NicovideoStoryboard.api.thumbnailInfo; this._watchController = window.NicovideoStoryboard.external.watchController; this._eventDispatcher = new EventDispatcher(); if (!this._watchController.isPremium()) { this._watchController.popup.alert('プレミアムの機能を使っているため、一般では動きません'); return; } this._storyboardController = new window.NicovideoStoryboard.controller.StoryboardController({ thumbnailInfo: this._thumbnailInfo, watchController: this._watchController, eventDispatcher: this._eventDispatcher, config: this.config }); this.initializeEvent(); this._addStyle(__css__, 'NicovideoStoryboardCss'); }, initializeEvent: function() { console.log('%c initializeEvent NicovideoStoryboard', 'background: lightgreen;'); var eventDispatcher = this._eventDispatcher; this._watchInfoModel.addEventListener('reset', function() { eventDispatcher.dispatchEvent('onWatchInfoReset'); }); this._playerAreaConnector.addEventListener('onVideoInitialized', function() { eventDispatcher.dispatchEvent('onVideoInitialized'); }); this._getflv.addEventListener('onGetflvLoadStart', function() { eventDispatcher.dispatchEvent('onGetflvLoadStart'); }); this._getflv.addEventListener('onGetflvLoad', function(info) { eventDispatcher.dispatchEvent('onGetflvLoad', info); }); this._thumbnailInfo.addEventListener('onThumbnailInfoLoadStart', function() { eventDispatcher.dispatchEvent('onThumbnailInfoLoadStart'); }); this._thumbnailInfo.addEventListener('onThumbnailInfoLoad', function(info) { eventDispatcher.dispatchEvent('onThumbnailInfoLoad', info); }); }, initializeUserConfig: function() { var prefix = 'NicoStoryboard_'; var conf = { enabled: true, autoScroll: true }; this.config = { get: function(key) { try { if (window.localStorage.hasOwnProperty(prefix + key)) { return JSON.parse(window.localStorage.getItem(prefix + key)); } return conf[key]; } catch (e) { return conf[key]; } }, set: function(key, value) { window.localStorage.setItem(prefix + key, JSON.stringify(value)); } }; }, load: function(watchId) { // 動画ごとのcookieがないと取得できないので指定できてもあまり意味は無い watchId = watchId || this._watchController.getWatchId(); this._storyboardController.load(watchId); } }); //====================================== //====================================== //====================================== (function() { var watchInfoModel = window.WatchApp.ns.model.WatchInfoModel.getInstance(); if (watchInfoModel.initialized) { console.log('%c initialize', 'background: lightgreen;'); window.NicovideoStoryboard.initialize(); } else { var onReset = function() { watchInfoModel.removeEventListener('reset', onReset); window.setTimeout(function() { watchInfoModel.removeEventListener('reset', onReset); console.log('%c initialize', 'background: lightgreen;'); window.NicovideoStoryboard.initialize(); }, 0); }; watchInfoModel.addEventListener('reset', onReset); } })(); }; var flapi = function() { if (window.name.indexOf('getflvLoader') < 0 ) { return; } var resp = document.documentElement.textContent; var origin = 'http://' + location.host.replace(/^.*?\./, 'www.'); try { parent.postMessage(JSON.stringify({ id: 'NicovideoStoryboard', type: 'getflv', body: { url: location.href, info: resp } }), origin); } catch (e) { alert(e); console.log('err', e); } }; var smileapi = function() { if (window.name.indexOf('StoryboardLoader') < 0 ) { return; } var resp = document.getElementsByTagName('smile'); var origin = 'http://' + location.host.replace(/^.*?\./, 'www.'); var xml = ''; if (resp.length > 0) { xml = resp[0].outerHTML; } try { parent.postMessage(JSON.stringify({ id: 'NicovideoStoryboard', type: 'storyboard', body: { url: location.href, xml: xml } }), origin); } catch (e) { console.log('err', e); } }; var host = window.location.host || ''; if (host === 'flapi.nicovideo.jp') { flapi(); } else if (host.indexOf('smile-') >= 0) { smileapi(); } else { var script = document.createElement('script'); script.id = 'NicoVideoStoryboard'; script.setAttribute('type', 'text/javascript'); script.setAttribute('charset', 'UTF-8'); script.appendChild(document.createTextNode("(" + monkey + ")()")); document.body.appendChild(script); } })();