NicovideoStoryboard

シークバーに出るサムネイルを並べて表示

当前为 2014-05-08 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name NicovideoStoryboard
  3. // @namespace https://github.com/segabito/
  4. // @description シークバーに出るサムネイルを並べて表示
  5. // @match http://www.nicovideo.jp/watch/*
  6. // @match http://flapi.nicovideo.jp/api/getflv*
  7. // @match http://*.nicovideo.jp/smile*
  8. // @grant none
  9. // @author segabito macmoto
  10. // @version 1.0.2
  11. // ==/UserScript==
  12.  
  13. // ver 1.0.1 フルスクリーンモードで開いた時はプレイヤー領域を押し上げるようにした
  14.  
  15. (function() {
  16. var monkey = function() {
  17. var DEBUG = !true;
  18. var $ = window.jQuery, _ = window._;
  19.  
  20. var __css__ = (function() {/*
  21. .xDomainLoaderFrame {
  22. position: fixed;
  23. top: -999px;
  24. left: -999px;
  25. width: 1px;
  26. height: 1px;
  27. border: 0;
  28. }
  29.  
  30. .storyboardContainer {
  31. position: fixed;
  32. bottom: -300px;
  33. left: 0;
  34. right: 0;
  35. width: 100%;
  36. box-sizing: border-box;
  37. -moz-box-sizing: border-box;
  38. -webkit-box-sizing: border-box;
  39. background: #999;
  40. border: 2px outset #000;
  41. z-index: 9005;
  42. overflow: visible;
  43. border-radius: 10px 10px 0 0;
  44. border-width: 2px 2px 0;
  45. box-shadow: 0 -2px 2px #666;
  46.  
  47. transition: bottom 0.5s ease-in-out;
  48. }
  49.  
  50. .storyboardContainer.show {
  51. bottom: 0;
  52. }
  53.  
  54. .storyboardContainer .storyboardInner {
  55. display: none;
  56. position: relative;
  57. text-align: center;
  58. overflow-x: scroll;
  59. white-space: nowrap;
  60. background: #222;
  61. margin: 4px 12px;
  62. border-style: inset;
  63. border-width: 2px 4px;
  64. border-radius: 10px 10px 0 0;
  65. }
  66.  
  67. .storyboardContainer.success .storyboardInner {
  68. display: block;
  69. }
  70.  
  71. .storyboardContainer .storyboardInner .boardList {
  72. overflow: hidden;
  73. }
  74.  
  75. .storyboardContainer .boardList .board {
  76. display: inline-block;
  77. cursor: pointer;
  78. background-color: #101010;
  79. }
  80.  
  81. .storyboardContainer.clicked .storyboardInner .boardList .board {
  82. cursor: wait;
  83. opacity: 0.5;
  84. }
  85.  
  86. .storyboardContainer .boardList .board.lazyImage {
  87. background-color: #ccc;
  88. }
  89. .storyboardContainer .boardList .board.loadFail {
  90. background-color: #c99;
  91. }
  92. .storyboardContainer .boardList .board.lazyImage {
  93. cursor: wait;
  94. }
  95.  
  96. .storyboardContainer .boardList .board > div {
  97. white-space: nowrap;
  98. }
  99. .storyboardContainer .boardList .board .border {
  100. box-sizing: border-box;
  101. -moz-box-sizing: border-box;
  102. -webkit-box-sizing: border-box;
  103. border-style: solid;
  104. border-color: #000 #333 #000 #999;
  105. border-width: 0 2px 0 2px;
  106. display: inline-block;
  107. }
  108. .storyboardContainer .boardList .board:hover .border {
  109. display: none; {* クリックできなくなっちゃうので苦し紛れの対策 もうちょっとマシな方法を考える *}
  110. }
  111.  
  112. .storyboardContainer .storyboardHeader {
  113. position: relative;
  114. width: 100%;
  115. }
  116. .storyboardContainer .pointer {
  117. position: absolute;
  118. bottom: -15px;
  119. left: 50%;
  120. width: 32px;
  121. margin-left: -16px;
  122. color: #333;
  123. z-index: 9010;
  124. text-align: center;
  125. }
  126.  
  127. .storyboardContainer .cursorTime {
  128. display: none;
  129. position: absolute;
  130. bottom: -30px;
  131. left: -999px;
  132. font-size: 10pt;
  133. border: 1px solid #000;
  134. z-index: 9010;
  135. background: #ffc;
  136. }
  137. .storyboardContainer:hover .cursorTime {
  138. display: block;
  139. }
  140.  
  141. .storyboardContainer .setToDisable {
  142. position: absolute;
  143. display: inline-block;
  144. left: 250px;
  145. bottom: -32px;
  146. transition: bottom 0.3s ease-in-out;
  147. }
  148. .storyboardContainer:hover .setToDisable {
  149. bottom: 0;
  150. }
  151.  
  152. .storyboardContainer .setToDisable button,
  153. .setToEnableButtonContainer button {
  154. background: none repeat scroll 0 0 #999;
  155. border-color: #666;
  156. border-radius: 18px 18px 0 0;
  157. border-style: solid;
  158. border-width: 2px 2px 0;
  159. width: 200px;
  160. overflow: auto;
  161. white-space: nowrap;
  162. cursor: pointer;
  163. box-shadow: 0 -2px 2px #666;
  164. }
  165.  
  166. .full_with_browser .setToEnableButtonContainer button {
  167. box-shadow: none;
  168. color: #888;
  169. background: #000;
  170. }
  171.  
  172. .full_with_browser .storyboardContainer .setToDisable,
  173. .full_with_browser .setToEnableButtonContainer {
  174. background: #000; {* Firefox対策 *}
  175. }
  176.  
  177. .setToEnableButtonContainer button {
  178. width: 200px;
  179. }
  180.  
  181. .storyboardContainer .setToDisable button:hover,
  182. .setToEnableButtonContainer:not(.loading):not(.fail) button:hover {
  183. background: #ccc;
  184. transition: none;
  185. }
  186.  
  187. .storyboardContainer .setToDisable button.clicked,
  188. .setToEnableButtonContainer.loading button,
  189. .setToEnableButtonContainer.fail button,
  190. .setToEnableButtonContainer button.clicked {
  191. border-style: inset;
  192. box-shadow: none;
  193. }
  194.  
  195. .setToEnableButtonContainer {
  196. position: fixed;
  197. z-index: 9003;
  198. left: 250px;
  199. bottom: 0px;
  200. transition: bottom 0.5s ease-in-out;
  201. }
  202. .setToEnableButtonContainer.loadingVideo {
  203. bottom: -50px;
  204. }
  205.  
  206. .setToEnableButtonContainer.loading *,
  207. .setToEnableButtonContainer.loadingVideo *{
  208. cursor: wait;
  209. font-size: 80%;
  210. }
  211. .setToEnableButtonContainer.fail {
  212. color: #999;
  213. cursor: default;
  214. font-size: 80%;
  215. }
  216.  
  217. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  218.  
  219. var storyboardTemplate = [
  220. '<div id="storyboardContainer" class="storyboardContainer">',
  221. '<div class="storyboardHeader">',
  222. '<div class="setToDisable"><button>閉じる ▼</button></div>',
  223. '<div class="pointer">▼</div>',
  224. '<div class="cursorTime"></div>',
  225. '</div>',
  226.  
  227. '<div class="storyboardInner"></div>',
  228. '<div class="failMessage">',
  229. '</div>',
  230. '</div>',
  231. '',
  232. ''].join('');
  233.  
  234. // マスコットキャラクターのサムネーヨちゃん
  235. var noThumbnailAA = (function() {/*
  236.  ∧ ∧     ┌─────────────
  237.  ( ´ー`)   < サムネーヨ
  238.  \ <     └───/|────────
  239.    \.\______//
  240.      \       /
  241.       ∪∪‾∪∪
  242. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  243.  
  244. var EventDispatcher = (function() {
  245.  
  246. function AsyncEventDispatcher() {
  247. window.WatchApp.extend(
  248. this,
  249. AsyncEventDispatcher,
  250. window.WatchApp.ns.event.EventDispatcher);
  251. }
  252.  
  253. window.WatchApp.mixin(AsyncEventDispatcher.prototype, {
  254. // addEventListener: function(name, func) {
  255. // console.log('%caddEventListener: ', 'background: red; color: white;', name, func);
  256. // if (!func) {
  257. // console.trace();
  258. // }
  259. // AsyncEventDispatcher.__super__.addEventListener.call(this, name, func);
  260. // },
  261. dispatchAsync: function() {
  262. var args = arguments;
  263.  
  264. window.setTimeout($.proxy(function() {
  265. try {
  266. this.dispatchEvent.apply(this, args);
  267. } catch (e) {
  268. console.log(e);
  269. }
  270. }, this), 0);
  271. }
  272. });
  273. return AsyncEventDispatcher;
  274. })();
  275.  
  276. window.NicovideoStoryboard =
  277. (function() {
  278. return {
  279. api: {},
  280. model: {},
  281. view: {},
  282. controller: {},
  283. external: {},
  284. event: {}
  285. };
  286. })();
  287.  
  288. // var console =
  289. // (function(debug) {
  290. // var n = window._.noop;
  291. // return debug ? window.console : {log: n, trace: n, time: n, timeEnd: n};
  292. // })(DEBUG);
  293. var console = window.console;
  294.  
  295. window.NicovideoStoryboard.event.windowEventDispatcher = (function() {
  296. var eventDispatcher = new EventDispatcher();
  297.  
  298. var onMessage = function(event) {
  299. if (event.origin.indexOf('nicovideo.jp') < 0) return;
  300. try {
  301. var data = JSON.parse(event.data);
  302. if (data.id !== 'NicovideoStoryboard') { return; }
  303.  
  304. eventDispatcher.dispatchEvent('onMessage', data.body, data.type);
  305. } catch (e) {
  306. console.log(
  307. '%cNicoVideoStoryboard.Error: window.onMessage - ',
  308. 'color: red; background: yellow',
  309. e
  310. );
  311. console.log('%corigin: ', 'background: yellow;', event.origin);
  312. console.log('%cdata: ', 'background: yellow;', event.data);
  313. console.trace();
  314. }
  315. };
  316.  
  317. window.addEventListener('message', onMessage);
  318.  
  319. return eventDispatcher;
  320. })();
  321.  
  322.  
  323. window.NicovideoStoryboard.external.watchController = (function() {
  324. var root = window.WatchApp.ns;
  325. var nicoPlayerConnector = root.init.PlayerInitializer.nicoPlayerConnector;
  326. var watchInfoModel = root.model.WatchInfoModel.getInstance();
  327. var viewerInfoModel = root.init.CommonModelInitializer.viewerInfoModel;
  328. var playerAreaConnector = root.init.PlayerInitializer.playerAreaConnector;
  329. var externalNicoplayer;
  330.  
  331. var watchController = new EventDispatcher();
  332.  
  333. var getVpos = function() {
  334. return nicoPlayerConnector.getVpos();
  335. };
  336. var setVpos = function(vpos) {
  337. nicoPlayerConnector.seekVideo(vpos);
  338. };
  339.  
  340. var _isPlaying = null;
  341. var isPlaying = function() {
  342. if (_isPlaying !== null) {
  343. return _isPlaying;
  344. }
  345. if (!externalNicoplayer) {
  346. externalNicoplayer = $("#external_nicoplayer")[0];
  347. }
  348. var status = externalNicoplayer.ext_getStatus();
  349. return status === 'playing';
  350. };
  351. var play = function() {
  352. nicoPlayerConnector.playVideo();
  353. };
  354. var pause = function() {
  355. nicoPlayerConnector.stopVideo();
  356. };
  357.  
  358. var isPremium = function() {
  359. return !!viewerInfoModel.isPremium;
  360. };
  361.  
  362. var getWatchId = function() {// スレッドIDだったりsmXXXXだったり
  363. return watchInfoModel.v;
  364. };
  365. var getVideoId = function() {// smXXXXXX, soXXXXX など
  366. return watchInfoModel.id;
  367. };
  368.  
  369. var popupMarquee = root.init.PopupMarqueeInitializer.popupMarqueeViewController;
  370. var popup = {
  371. message: function(text) {
  372. popupMarquee.onData(
  373. '<span style="background: black;">' + text + '</span>'
  374. );
  375. },
  376. alert: function(text) {
  377. popupMarquee.onData(
  378. '<span style="background: black; color: red;">' + text + '</span>'
  379. );
  380. }
  381. };
  382.  
  383. playerAreaConnector.addEventListener('onVideoPlayed', function() {
  384. _isPlaying = true;
  385. watchController.dispatchEvent('onVideoPlayed');
  386. });
  387. playerAreaConnector.addEventListener('onVideoStopped', function() {
  388. _isPlaying = false;
  389. watchController.dispatchEvent('onVideoStopped');
  390. });
  391.  
  392. playerAreaConnector.addEventListener('onVideoStarted', function() {
  393. _isPlaying = true;
  394. watchController.dispatchEvent('onVideoStarted');
  395. });
  396. playerAreaConnector.addEventListener('onVideoEnded', function() {
  397. _isPlaying = false;
  398. watchController.dispatchEvent('onVideoEnded');
  399. });
  400.  
  401. playerAreaConnector.addEventListener('onVideoSeeked', function() {
  402. watchController.dispatchEvent('onVideoSeeked');
  403. });
  404.  
  405.  
  406. window.WatchApp.mixin(watchController, {
  407. getVpos: getVpos,
  408. setVpos: setVpos,
  409.  
  410. isPlaying: isPlaying,
  411. play: play,
  412. pause: pause,
  413.  
  414. isPremium: isPremium,
  415.  
  416. getWatchId: getWatchId,
  417. getVideoId: getVideoId,
  418.  
  419. popup: popup
  420. });
  421.  
  422. return watchController;
  423. })();
  424.  
  425.  
  426. window.NicovideoStoryboard.api.getflv = (function() {
  427. var BASE_URL = 'http://flapi.nicovideo.jp/api/getflv?v=';
  428. var loaderFrame, loaderWindow, cache = {};
  429. var eventDispatcher = window.NicovideoStoryboard.event.windowEventDispatcher;
  430. var getflv = new EventDispatcher();
  431.  
  432. var parseInfo = function(q) {
  433. var info = {}, lines = q.split('&');
  434. $.each(lines, function(i, line) {
  435. var tmp = line.split('=');
  436. var key = window.unescape(tmp[0]), value = window.unescape(tmp[1]);
  437. info[key] = value;
  438. });
  439. return info;
  440. };
  441.  
  442. var onMessage = function(data, type) {
  443. if (type !== 'getflv') { return; }
  444. var info = parseInfo(data.info), url = data.url;
  445.  
  446. cache[url] = info;
  447. //console.log('getflv.onGetflvLoad', info);
  448. getflv.dispatchAsync('onGetflvLoad', info);
  449. };
  450.  
  451. var initialize = function() {
  452. initialize = _.noop;
  453.  
  454. console.log('%c initialize getflv', 'background: lightgreen;');
  455.  
  456. loaderFrame = document.createElement('iframe');
  457. loaderFrame.name = 'getflvLoader';
  458. loaderFrame.className = DEBUG ? 'xDomainLoaderFrame debug' : 'xDomainLoaderFrame';
  459. document.body.appendChild(loaderFrame);
  460.  
  461. loaderWindow = loaderFrame.contentWindow;
  462.  
  463. eventDispatcher.addEventListener('onMessage', onMessage);
  464. };
  465.  
  466. var load = function(watchId) {
  467. initialize();
  468. var url = BASE_URL + watchId;
  469. //console.log('getflv: ', url);
  470.  
  471. getflv.dispatchEvent('onGetflvLoadStart', watchId);
  472. if (cache[url]) {
  473. //console.log('%cgetflv cache exist', 'background: cyan', url);
  474. getflv.dispatchAsync('onGetflvLoad', cache[url]);
  475. } else {
  476. loaderWindow.location.replace(url);
  477. }
  478. };
  479.  
  480. window.WatchApp.mixin(getflv, {
  481. load: load
  482. });
  483.  
  484. return getflv;
  485. })();
  486.  
  487. window.NicovideoStoryboard.api.thumbnailInfo = (function() {
  488. var getflv = window.NicovideoStoryboard.api.getflv;
  489. var loaderFrame, loaderWindow, cache = {};
  490. var eventDispatcher = window.NicovideoStoryboard.event.windowEventDispatcher;
  491. var thumbnailInfo = new EventDispatcher();
  492.  
  493. var onGetflvLoad = function(info) {
  494. //console.log('thumbnailInfo.onGetflvLoad', info);
  495. if (!info.url) {
  496. thumbnailInfo.dispatchAsync(
  497. 'onThumbnailInfoLoad',
  498. {status: 'ng', message: 'サムネイル情報の取得に失敗しました'}
  499. );
  500. return;
  501. } else
  502. if (info.url.indexOf('http://') !== 0) { // rtmpe:~など
  503. thumbnailInfo.dispatchAsync(
  504. 'onThumbnailInfoLoad',
  505. {status: 'ng', message: 'この配信形式には対応していません'}
  506. );
  507. return;
  508. }
  509.  
  510. var url = info.url + '&sb=1';
  511.  
  512. thumbnailInfo.dispatchEvent('onThumbnailInfoLoadStart');
  513. if (cache[url]) {
  514. //console.log('%cthumbnailInfo cache exist', 'background: cyan', url);
  515. thumbnailInfo.dispatchAsync('onThumbnailInfoLoad', cache[url]);
  516. return;
  517. }
  518. loaderWindow.location.replace(url);
  519. };
  520.  
  521. var onMessage = function(data, type) {
  522. if (type !== 'storyboard') { return; }
  523. //console.log('thumbnailInfo.onMessage: ', data, type);
  524.  
  525. var url = data.url;
  526. var xml = data.xml, $xml = $(xml), $storyboard = $xml.find('storyboard');
  527.  
  528. if ($storyboard.length < 1) {
  529. thumbnailInfo.dispatchAsync(
  530. 'onThumbnailInfoLoad',
  531. {status: 'ng', message: 'この動画にはサムネイルがありません'}
  532. );
  533. return;
  534. }
  535. var info = {
  536. status: 'ok',
  537. message: '成功',
  538. url: data.url,
  539. movieId: $xml.find('movie').attr('id'),
  540. duration: $xml.find('duration').text(),
  541. thumbnail:{
  542. width: $storyboard.find('thumbnail_width').text(),
  543. height: $storyboard.find('thumbnail_height').text(),
  544. number: $storyboard.find('thumbnail_number').text(),
  545. interval: $storyboard.find('thumbnail_interval').text()
  546. },
  547. board: {
  548. rows: $storyboard.find('board_rows').text(),
  549. cols: $storyboard.find('board_cols').text(),
  550. number: $storyboard.find('board_number').text()
  551. }
  552. };
  553.  
  554. cache[url] = info;
  555. thumbnailInfo.dispatchAsync('onThumbnailInfoLoad', info);
  556. };
  557.  
  558. var initialize = function() {
  559. initialize = _.noop;
  560.  
  561. console.log('%c initialize thumbnailInfo', 'background: lightgreen;');
  562.  
  563. loaderFrame = document.createElement('iframe');
  564. loaderFrame.name = 'StoryboardLoader';
  565. loaderFrame.className = DEBUG ? 'xDomainLoaderFrame debug' : 'xDomainLoaderFrame';
  566. document.body.appendChild(loaderFrame);
  567.  
  568. loaderWindow = loaderFrame.contentWindow;
  569.  
  570. eventDispatcher.addEventListener('onMessage', onMessage);
  571. getflv.addEventListener('onGetflvLoad', onGetflvLoad);
  572. };
  573.  
  574. var load = function(watchId) {
  575. initialize();
  576. getflv.load(watchId);
  577. };
  578.  
  579.  
  580. window.WatchApp.mixin(thumbnailInfo, {
  581. load: load
  582. });
  583.  
  584. return thumbnailInfo;
  585. })();
  586.  
  587. window.NicovideoStoryboard.model.StoryboardModel = (function() {
  588.  
  589. function StoryboardModel(params) {
  590. this._thumbnailInfo = params.thumbnailInfo;
  591. this._isEnabled = params.isEnabled;
  592. this._watchId = params.watchId;
  593.  
  594. window.WatchApp.extend(this, StoryboardModel, EventDispatcher);
  595. }
  596.  
  597. window.WatchApp.mixin(StoryboardModel.prototype, {
  598. initialize: function(info) {
  599. console.log('%c initialize StoryboardModel', 'background: lightgreen;');
  600.  
  601. this.update(info);
  602. },
  603. update: function(info) {
  604. if (info.status !== 'ok') {
  605. window.WatchApp.mixin(info, {
  606. url: '',
  607. width: 1,
  608. height: 1,
  609. duration: 1,
  610. thumbnail: {
  611. width: 1,
  612. height: 1,
  613. number: 1,
  614. interval: 1
  615. },
  616. board: {
  617. rows: 1,
  618. cols: 1
  619. }
  620. });
  621. }
  622. this._info = info;
  623.  
  624. this.dispatchEvent('update');
  625. },
  626.  
  627. reset: function() {
  628. if (this.isEnabled()) {
  629. window.setTimeout($.proxy(function() {
  630. this.load();
  631. }, this), 1000);
  632. }
  633. this.dispatchEvent('reset');
  634. },
  635.  
  636. load: function() {
  637. this._isEnabled = true;
  638. this._thumbnailInfo.load(this._watchId);
  639. },
  640.  
  641. setWatchId: function(watchId) {
  642. this._watchId = watchId;
  643. },
  644.  
  645. unload: function() {
  646. this._isEnabled = false;
  647. this.dispatchEvent('unload');
  648. },
  649.  
  650. isEnabled: function() {
  651. return this._isEnabled;
  652. },
  653.  
  654. getStatus: function() { return this._info.status; },
  655. getMessage: function() { return this._info.message; },
  656. getUrl: function() { return this._info.url; },
  657. getDuration: function() { return parseInt(this._info.duration, 10); },
  658.  
  659. getWidth: function() { return parseInt(this._info.thumbnail.width, 10); },
  660. getHeight: function() { return parseInt(this._info.thumbnail.height, 10); },
  661. getInterval: function() { return parseInt(this._info.thumbnail.interval, 10); },
  662. getCount: function() {
  663. return Math.max(
  664. Math.ceil(this.getDuration() / Math.max(0.01, this.getInterval())),
  665. parseInt(this._info.thumbnail.number, 10)
  666. );
  667. },
  668. getRows: function() { return parseInt(this._info.board.rows, 10); },
  669. getCols: function() { return parseInt(this._info.board.cols, 10); },
  670. getPageCount: function() { return parseInt(this._info.board.number, 10); },
  671. getTotalRows: function() {
  672. return Math.ceil(this.getCount() / this.getCols());
  673. },
  674.  
  675. getPageWidth: function() { return this.getWidth() * this.getCols(); },
  676. getPageHeight: function() { return this.getHeight() * this.getRows(); },
  677. getCountPerPage: function() { return this.getRows() * this.getCols(); },
  678.  
  679. /**
  680. * nページ目のURLを返す。 ゼロオリジン
  681. */
  682. getPageUrl: function(page) {
  683. page = Math.max(0, Math.min(this.getPageCount() - 1, page));
  684. return this.getUrl() + '&board=' + (page + 1);
  685. },
  686.  
  687. /**
  688. * vposに相当するサムネは何番目か?を返す
  689. */
  690. getIndex: function(vpos) {
  691. // msec -> sec
  692. var v = Math.floor(vpos / 1000);
  693. v = Math.max(0, Math.min(this.getDuration(), v));
  694.  
  695. // サムネの総数 ÷ 秒数
  696. // Math.maxはゼロ除算対策
  697. var n = this.getCount() / Math.max(1, this.getDuration());
  698.  
  699. return parseInt(Math.floor(v * n), 10);
  700. },
  701.  
  702. /**
  703. * Indexのサムネイルは何番目のページにあるか?を返す
  704. */
  705. getPageIndex: function(thumbnailIndex) {
  706. var perPage = this.getCountPerPage();
  707. var pageIndex = parseInt(thumbnailIndex / perPage, 10);
  708. return Math.max(0, Math.min(this.getPageCount(), pageIndex));
  709. },
  710.  
  711. /**
  712. * vposに相当するサムネは何ページの何番目にあるか?を返す
  713. */
  714. getThumbnailPosition: function(vpos) {
  715. var thumbnailIndex = this.getIndex(vpos);
  716. var pageIndex = this.getPageIndex(thumbnailIndex);
  717.  
  718. var mod = thumbnailIndex % this.getCountPerPage();
  719. var row = Math.floor(mod / Math.max(1, this.getCols()));
  720. var col = mod % this.getRows();
  721.  
  722. return {
  723. page: pageIndex,
  724. index: thumbnailIndex,
  725. row: row,
  726. col: col
  727. };
  728. },
  729.  
  730. /**
  731. * nページ目のx, y座標をvposに変換して返す
  732. */
  733. getPointVpos: function(x, y, page) {
  734. var width = Math.max(1, this.getWidth());
  735. var height = Math.max(1, this.getHeight());
  736. var row = Math.floor(y / height);
  737. var col = Math.floor(x / width);
  738. var mod = x % width;
  739.  
  740.  
  741. // 何番目のサムネに相当するか?
  742. var point =
  743. page * this.getCountPerPage() +
  744. row * this.getCols() +
  745. col +
  746. (mod / width) // 小数点以下は、n番目の左端から何%あたりか
  747. ;
  748.  
  749. // 全体の何%あたり?
  750. var percent = point / Math.max(1, this.getCount());
  751. percent = Math.max(0, Math.min(100, percent));
  752.  
  753. // vposは㍉秒単位なので1000倍
  754. return Math.floor(this.getDuration() * percent * 1000);
  755. },
  756.  
  757. /**
  758. * vposは何ページ目に当たるか?を返す
  759. */
  760. getVposPage: function(vpos) {
  761. var index = this._storyboard.getIndex(vpos);
  762. var page = this._storyboard.getPageIndex(index);
  763.  
  764. return page;
  765. }
  766.  
  767. });
  768.  
  769. return StoryboardModel;
  770. })();
  771.  
  772. window.NicovideoStoryboard.view.FullScreenModeView = (function() {
  773. var __TEMPLATE__ = (function() {/*
  774. body.full_with_browser{
  775. background: #000;
  776. }
  777. body.full_with_browser.NicovideoStoryboardOpen #content{
  778. margin-bottom: {$storyboardHeight}px;
  779. transition: margin-bottom 0.5s ease-in-out;
  780. }
  781. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  782.  
  783. var addStyle = function(styles, id) {
  784. var elm = document.createElement('style');
  785. window.setTimeout(function() {
  786. elm.type = 'text/css';
  787. if (id) { elm.id = id; }
  788.  
  789. var text = styles.toString();
  790. text = document.createTextNode(text);
  791. elm.appendChild(text);
  792. var head = document.getElementsByTagName('head');
  793. head = head[0];
  794. head.appendChild(elm);
  795. }, 0);
  796. return elm;
  797. };
  798.  
  799. function FullScreenModeView() {
  800.  
  801. this._css = null;
  802. this._lastHeight = -1;
  803. }
  804.  
  805. window.WatchApp.mixin(FullScreenModeView.prototype, {
  806. initialize: function() {
  807. if (this._css) { return; }
  808.  
  809. console.log('%cinitialize NicovideoStorybaordFullScreenStyle', 'background: lightgreen;');
  810. this._css = addStyle('/* undefined */', 'NicovideoStorybaordFullScreenStyle');
  811. },
  812. update: function($container) {
  813. this.initialize();
  814.  
  815. var height = $container.outerHeight();
  816.  
  817. if (height === this._lastHeight) { return; }
  818. this._lastHeight = height;
  819.  
  820. var newCss = __TEMPLATE__.replace('{$storyboardHeight}', height);
  821. this._css.innerHTML = newCss;
  822. }
  823. });
  824.  
  825.  
  826. return FullScreenModeView;
  827. })();
  828.  
  829. window.NicovideoStoryboard.view.SetToEnableButtonView = (function() {
  830.  
  831. var TEXT = {
  832. DEFAULT: 'サムネイルを開く ▲',
  833. LOADING: '動画を読み込み中...',
  834. GETFLV: '動画情報を読み込み中...',
  835. THUMBNAIL: 'サムネイル情報を読み込み中...'
  836. };
  837.  
  838.  
  839. function SetToEnableButtonView(params) {
  840. this._storyboard = params.storyboard;
  841. this._eventDispatcher = params.eventDispatcher;
  842.  
  843. this.initialize();
  844. }
  845.  
  846. window.WatchApp.mixin(SetToEnableButtonView.prototype, {
  847. initialize: function() {
  848. console.log('%cinitialize SetToEnableButtonView', 'background: lightgreen;');
  849. this._$view = $([
  850. '<div class="setToEnableButtonContainer loadingVideo">',
  851. '<button>', TEXT.DEFAULT, '</button>',
  852. '</div>',
  853. '',
  854. '',
  855. ''].join(''));
  856. this._$button = this._$view.find('button');
  857. this._$button.on('click', $.proxy(this._onButtonClick, this));
  858.  
  859. var sb = this._storyboard;
  860. sb.addEventListener('reset', $.proxy(this._onStoryboardReset, this));
  861. sb.addEventListener('update', $.proxy(this._onStoryboardUpdate, this));
  862.  
  863. var evt = this._eventDispatcher;
  864. evt.addEventListener('getFlvLoadStart',
  865. $.proxy(this._onGetflvLoadStart, this));
  866. evt.addEventListener('onThumbnailInfoLoadStart',
  867. $.proxy(this._onThumbnailInfoLoadStart, this));
  868. evt.addEventListener('onWatchInfoReset',
  869. $.proxy(this._onWatchInfoReset, this));
  870.  
  871. $('body').append(this._$view);
  872. },
  873. reset: function() {
  874. this._$view.attr('title', '');
  875. if (this._storyboard.isEnabled()) {
  876. this._$view.removeClass('loadingVideo getflv thumbnailInfo fail success').addClass('loading');
  877. this._setText(TEXT.GETFLV);
  878. } else {
  879. this._$view.removeClass('loadingVideo getflv thumbnailInfo fail success loading');
  880. this._setText(TEXT.DEFAULT);
  881. }
  882. },
  883. _setText: function(text) {
  884. this._$button.text(text);
  885. },
  886. _onButtonClick: function(e) {
  887. if (
  888. this._$view.hasClass('loading') ||
  889. this._$view.hasClass('loadingVideo') ||
  890. this._$view.hasClass('fail')) {
  891. return;
  892. }
  893. e.preventDefault();
  894. e.stopPropagation();
  895.  
  896. var $view = this._$view.addClass('loading clicked');
  897. this._eventDispatcher.dispatchEvent('onEnableStoryboard');
  898. window.setTimeout(function() {
  899. $view.removeClass('clicked');
  900. $view = null;
  901. }, 1000);
  902. },
  903. _onStoryboardReset: function() {
  904. this.reset();
  905. },
  906. _onStoryboardUpdate: function() {
  907. var storyboard = this._storyboard;
  908.  
  909. if (storyboard.getStatus() === 'ok') {
  910. window.setTimeout($.proxy(function() {
  911. this._$view
  912. .removeClass('loading getflv thumbnailInfo')
  913. .addClass('success')
  914. .attr('title', '');
  915. this._setText(TEXT.DEFAULT);
  916. }, this), 3000);
  917. } else {
  918. this._$view
  919. .removeClass('loading')
  920. .addClass('fail')
  921. .attr('title', DEBUG ? noThumbnailAA : '');
  922. this._setText(storyboard.getMessage());
  923. }
  924. },
  925. _onGetflvLoadStart: function() {
  926. this._$view.addClass('loading getflv');
  927. this._setText(TEXT.GETFLV);
  928. },
  929. _onThumbnailInfoLoadStart: function() {
  930. this._$view.addClass('loading thumbnailInfo');
  931. this._setText(TEXT.THUMBNAIL);
  932. },
  933. _onWatchInfoReset: function() {
  934. this._$view.addClass('loadingVideo');
  935. this._setText(TEXT.LOADING);
  936. }
  937. });
  938.  
  939. return SetToEnableButtonView;
  940. })();
  941.  
  942. window.NicovideoStoryboard.view.StoryboardView = (function() {
  943. var TIMER_INTERVAL = 33;
  944. var VPOS_RATE = 10;
  945.  
  946. function StoryboardView(params) {
  947. this.initialize(params);
  948. }
  949.  
  950. window.WatchApp.mixin(StoryboardView.prototype, {
  951. initialize: function(params) {
  952. console.log('%c initialize StoryboardView', 'background: lightgreen;');
  953.  
  954. this._watchController = params.watchController;
  955. var evt = this._eventDispatcher = params.eventDispatcher;
  956. var sb = this._storyboard = params.storyboard;
  957.  
  958. this._isHover = false;
  959. this._currentUrl = '';
  960. this._lazyImage = {};
  961. this._lastPage = -1;
  962. this._lastVpos = 0;
  963. this._lastGetVpos = 0;
  964. this._timerCount = 0;
  965. this._scrollLeft = 0;
  966.  
  967. this._enableButtonView =
  968. new window.NicovideoStoryboard.view.SetToEnableButtonView({
  969. storyboard: sb,
  970. eventDispatcher: this._eventDispatcher
  971. });
  972.  
  973. this._fullScreenModeView =
  974. new window.NicovideoStoryboard.view.FullScreenModeView();
  975.  
  976. evt.addEventListener('onWatchInfoReset', $.proxy(this._onWatchInfoReset, this));
  977.  
  978. sb.addEventListener('update', $.proxy(this._onStoryboardUpdate, this));
  979. sb.addEventListener('reset', $.proxy(this._onStoryboardReset, this));
  980. sb.addEventListener('unload', $.proxy(this._onStoryboardUnload, this));
  981. },
  982. _initializeStoryboard: function() {
  983. this._initializeStoryboard = _.noop;
  984. console.log('%cStoryboardView.initializeStoryboard', 'background: lightgreen;');
  985.  
  986. var $view = this._$view = $(storyboardTemplate);
  987.  
  988. var $inner = this._$inner = $view.find('.storyboardInner');
  989. this._$failMessage = $view.find('.failMessage');
  990. this._$cursorTime = $view.find('.cursorTime');
  991. this._$disableButton = $view.find('.setToDisable button');
  992.  
  993. var self = this;
  994. var onHoverIn = function() { self._isHover = true; };
  995. var onHoverOut = function() { self._isHover = false; };
  996. $view
  997. .on('click', '.board', $.proxy(this._onBoardClick, this))
  998. .on('mousemove', '.board', $.proxy(this._onBoardMouseMove, this))
  999. .on('mousewheel', $.proxy(this._onMouseWheel, this));
  1000.  
  1001. $inner
  1002. .hover(onHoverIn, onHoverOut)
  1003. .on('scroll', _.throttle(function() { self._onScroll(); }, 500));
  1004.  
  1005. this._watchController
  1006. .addEventListener('onVideoSeeked', $.proxy(this._onVideoSeeked, this));
  1007.  
  1008. this._$disableButton.on('click',
  1009. $.proxy(this._onDisableButtonClick, this));
  1010.  
  1011. $('body').append($view);
  1012. },
  1013. _onBoardClick: function(e) {
  1014. var $board = $(e.target), offset = $board.offset();
  1015. var y = $board.attr('data-top') * 1;
  1016. var x = e.pageX - offset.left;
  1017. var page = $board.attr('data-page');
  1018. var vpos = this._storyboard.getPointVpos(x, y, page);
  1019. if (isNaN(vpos)) { return; }
  1020.  
  1021. var $view = this._$view;
  1022. $view.addClass('clicked');
  1023. window.setTimeout(function() { $view.removeClass('clicked'); }, 300);
  1024. this._eventDispatcher.dispatchEvent('onStoryboardSelect', vpos);
  1025.  
  1026. this._isHover = false;
  1027. if ($board.hasClass('lazyImage')) { this._lazyLoadImage(page); }
  1028. },
  1029. _onBoardMouseMove: function(e) {
  1030. var $board = $(e.target), offset = $board.offset();
  1031. var y = $board.attr('data-top') * 1;
  1032. var x = e.pageX - offset.left;
  1033. var page = $board.attr('data-page');
  1034. var vpos = this._storyboard.getPointVpos(x, y, page);
  1035. if (isNaN(vpos)) { return; }
  1036. var sec = Math.floor(vpos / 1000);
  1037.  
  1038. var time = Math.floor(sec / 60) + ':' + ((sec % 60) + 100).toString().substr(1);
  1039. this._$cursorTime.text(time).css({left: e.pageX});
  1040.  
  1041. this._isHover = true;
  1042. if ($board.hasClass('lazyImage')) { this._lazyLoadImage(page); }
  1043. },
  1044. _onMouseWheel: function(e, delta) {
  1045. e.preventDefault();
  1046. e.stopPropagation();
  1047. this._isHover = true;
  1048. var left = this.scrollLeft();
  1049. this.scrollLeft(left - delta * 140);
  1050. },
  1051. _onVideoSeeked: function() {
  1052. if (!this._storyboard.isEnabled()) {
  1053. return;
  1054. }
  1055. if (this._storyboard.getStatus() !== 'ok') {
  1056. return;
  1057. }
  1058. var vpos = this._watchController.getVpos();
  1059. var page = this._storyboard.getVposPage(vpos);
  1060.  
  1061. this._lazyLoadImage(page);
  1062. if (this.isHover || !this._watchController.isPlaying()) {
  1063. this._onVposUpdate(vpos, true);
  1064. } else {
  1065. this._onVposUpdate(vpos);
  1066. }
  1067. },
  1068. update: function() {
  1069. this.disableTimer();
  1070.  
  1071. this._initializeStoryboard();
  1072. this._$view.removeClass('show success');
  1073. $('body').removeClass('NicovideoStoryboardOpen');
  1074. if (this._storyboard.getStatus() === 'ok') {
  1075. this._updateSuccess();
  1076. } else {
  1077. this._updateFail();
  1078. }
  1079. },
  1080. scrollLeft: function(left) {
  1081. if (left === undefined) {
  1082. return this._scrollLeft;
  1083. } else
  1084. if (left === 0 || Math.abs(this._scrollLeft - left) >= 1) {
  1085. this._$inner[0].scrollLeft = left;
  1086. this._scrollLeft = left;
  1087. }
  1088. },
  1089. _updateSuccess: function() {
  1090. var url = this._storyboard.getUrl();
  1091. if (this._currentUrl === url) {
  1092. this._$view.addClass('show success');
  1093. this.enableTimer();
  1094. } else {
  1095. this._currentUrl = url;
  1096. this._updateSuccessFull();
  1097. }
  1098. $('body').addClass('NicovideoStoryboardOpen');
  1099. },
  1100. _updateSuccessFull: function() {
  1101. var storyboard = this._storyboard;
  1102. var pages = storyboard.getPageCount();
  1103. var pageWidth = storyboard.getPageWidth();
  1104. var height = storyboard.getHeight();
  1105. var rows = storyboard.getRows();
  1106.  
  1107. var $borders =
  1108. this._createBorders(storyboard.getWidth(), storyboard.getHeight(), storyboard.getCols());
  1109.  
  1110. var totalRows = storyboard.getTotalRows();
  1111. var rowCnt = 0;
  1112. var $list = $('<div class="boardList"/>')
  1113. .css({
  1114. width: storyboard.getCount() * storyboard.getWidth(),
  1115. paddingLeft: '50%',
  1116. paddingRight: '50%',
  1117. height: height
  1118. });
  1119.  
  1120. for (var i = 0; i < pages; i++) {
  1121. var src = storyboard.getPageUrl(i);
  1122. for (var j = 0; j < rows; j++) {
  1123. var $img =
  1124. $('<div class="board"/>')
  1125. .css({
  1126. width: pageWidth,
  1127. height: height,
  1128. backgroundPosition: '0 -' + height * j + 'px'
  1129. })
  1130. .attr({
  1131. 'data-src': src,
  1132. 'data-page': i,
  1133. 'data-top': height * j + height / 2
  1134. })
  1135. .append($borders.clone());
  1136.  
  1137. if (i === 0) { // 1ページ目だけ遅延ロードしない
  1138. $img.css('background-image', 'url(' + src + ')');
  1139. } else {
  1140. $img.addClass('lazyImage page-' + i);
  1141. }
  1142. $list.append($img);
  1143. rowCnt++;
  1144. if (rowCnt >= totalRows) {
  1145. break;
  1146. }
  1147. }
  1148. }
  1149.  
  1150. this._$innerList = $list;
  1151.  
  1152. this._$inner.empty().append($list).append(this._$pointer);
  1153. this._$view.removeClass('fail').addClass('success');
  1154.  
  1155. this._fullScreenModeView.update(this._$view);
  1156.  
  1157. window.setTimeout($.proxy(function() {
  1158. this._$view.addClass('show');
  1159. }, this), 100);
  1160.  
  1161. this.scrollLeft(0);
  1162. this.enableTimer();
  1163. },
  1164. _createBorders: function(width, height, count) {
  1165. var $border = $('<div class="border"/>').css({
  1166. width: width,
  1167. height: height
  1168. });
  1169. var $div = $('<div />');
  1170. for (var i = 0; i < count; i++) {
  1171. $div.append($border.clone());
  1172. }
  1173. return $div;
  1174. },
  1175. _lazyLoadImage: function(pageNumber) {
  1176. var className = 'page-' + pageNumber;
  1177.  
  1178. if (pageNumber < 1 || this._lazyImage[className]) {
  1179. return;
  1180. }
  1181.  
  1182. var src = this._storyboard.getPageUrl(pageNumber);
  1183. this._lazyImage[className] = src;
  1184.  
  1185. //console.log('%c set lazyLoadImage', 'background: cyan;', 'page: ' + pageNumber, ' url: ' + src);
  1186.  
  1187. var load = $.proxy(function() {
  1188. this._$inner.find('.' + className)
  1189. .css('background-image', 'url(' + src + ')')
  1190. .removeClass('lazyImage ' + className);
  1191. }, this);
  1192.  
  1193. window.setTimeout(load, 0);
  1194. //window.setTimeout(load, 1000);
  1195. },
  1196. _updateFail: function() {
  1197. this._$view.removeClass('success').addClass('fail');
  1198. this.disableTimer();
  1199. },
  1200. clear: function() {
  1201. if (this._$view) {
  1202. this._$inner.empty();
  1203. }
  1204. this.disableTimer();
  1205. },
  1206. _clearTimer: function() {
  1207. if (this._timer) {
  1208. window.clearInterval(this._timer);
  1209. this._timer = null;
  1210. }
  1211. },
  1212. enableTimer: function() {
  1213. this._clearTimer();
  1214. this._isHover = false;
  1215. this._timer = window.setInterval($.proxy(this._onTimerInterval, this), TIMER_INTERVAL);
  1216. },
  1217. disableTimer: function() {
  1218. this._clearTimer();
  1219. },
  1220. _onTimerInterval: function() {
  1221. if (this._isHover) { return; }
  1222. if (!this._storyboard.isEnabled()) { return; }
  1223.  
  1224. var div = VPOS_RATE;
  1225. var mod = this._timerCount % div;
  1226. this._timerCount++;
  1227.  
  1228. var vpos;
  1229.  
  1230. if (!this._watchController.isPlaying()) {
  1231. return;
  1232. }
  1233.  
  1234. // getVposが意外に時間を取るので回数を減らす
  1235. // そもそもコメントパネルがgetVpos叩きまくってるんですがそれは
  1236. if (mod === 0) {
  1237. vpos = this._watchController.getVpos();
  1238. } else {
  1239. vpos = this._lastVpos;
  1240. }
  1241.  
  1242. this._onVposUpdate(vpos);
  1243. },
  1244. _onVposUpdate: function(vpos, isImmediately) {
  1245. var storyboard = this._storyboard;
  1246. var duration = Math.max(1, storyboard.getDuration());
  1247. var per = vpos / (duration * 1000);
  1248. var width = storyboard.getWidth();
  1249. var boardWidth = storyboard.getCount() * width;
  1250. var targetLeft = boardWidth * per + width * 0.4;
  1251. var currentLeft = this.scrollLeft();
  1252. var leftDiff = targetLeft - currentLeft;
  1253.  
  1254. if (Math.abs(leftDiff) > 5000) {
  1255. leftDiff = leftDiff * 0.93; // 大きくシークした時
  1256. } else {
  1257. leftDiff = leftDiff / VPOS_RATE;
  1258. }
  1259.  
  1260. this._lastVpos = vpos;
  1261.  
  1262. this.scrollLeft(isImmediately ? targetLeft : (currentLeft + Math.round(leftDiff)));
  1263.  
  1264. },
  1265. _onScroll: function() {
  1266. var storyboard = this._storyboard;
  1267. var scrollLeft = this.scrollLeft();
  1268. var page = Math.round(scrollLeft / (storyboard.getPageWidth() * storyboard.getRows()));
  1269. this._lazyLoadImage(Math.min(page, storyboard.getPageCount() - 1));
  1270. },
  1271. reset: function() {
  1272. this._lastVpos = -1;
  1273. this._lastPage = -1;
  1274. this._currentUrl = '';
  1275. this._timerCount = 0;
  1276. this._scrollLeft = 0;
  1277. this._lazyImage = {};
  1278. if (this._$view) {
  1279. $('body').removeClass('NicovideoStoryboardOpen');
  1280. this._$view.removeClass('show');
  1281. this._$inner.empty();
  1282. }
  1283. },
  1284. _onDisableButtonClick: function(e) {
  1285. e.preventDefault();
  1286. e.stopPropagation();
  1287.  
  1288. var $button = this._$disableButton;
  1289. $button.addClass('clicked');
  1290. window.setTimeout(function() {
  1291. $button.removeClass('clicked');
  1292. }, 1000);
  1293.  
  1294. this._eventDispatcher.dispatchEvent('onDisableStoryboard');
  1295. },
  1296. _onStoryboardUpdate: function() {
  1297. this.update();
  1298. },
  1299. _onStoryboardReset: function() {
  1300. },
  1301. _onStoryboardUnload: function() {
  1302. $('body').removeClass('NicovideoStoryboardOpen');
  1303. if (this._$view) {
  1304. this._$view.removeClass('show');
  1305. }
  1306. },
  1307. _onWatchInfoReset: function() {
  1308. this.reset();
  1309. }
  1310. });
  1311.  
  1312. return StoryboardView;
  1313. })();
  1314.  
  1315. window.NicovideoStoryboard.controller.StoryboardController = (function() {
  1316.  
  1317. function StoryboardController(params) {
  1318. this.initialize(params);
  1319. }
  1320.  
  1321. window.WatchApp.mixin(StoryboardController.prototype, {
  1322. initialize: function(params) {
  1323. console.log('%c initialize StoryboardController', 'background: lightgreen;');
  1324.  
  1325. this._thumbnailInfo = params.thumbnailInfo;
  1326. this._watchController = params.watchController;
  1327. this._config = params.config;
  1328.  
  1329. var evt = this._eventDispatcher = params.eventDispatcher;
  1330.  
  1331. evt.addEventListener('onVideoInitialized',
  1332. $.proxy(this._onVideoInitialized, this));
  1333.  
  1334. evt.addEventListener('onWatchInfoReset',
  1335. $.proxy(this._onWatchInfoReset, this));
  1336.  
  1337. evt.addEventListener('onStoryboardSelect',
  1338. $.proxy(this._onStoryboardSelect, this));
  1339.  
  1340. evt.addEventListener('onEnableStoryboard',
  1341. $.proxy(this._onEnableStoryboard, this));
  1342.  
  1343. evt.addEventListener('onDisableStoryboard',
  1344. $.proxy(this._onDisableStoryboard, this));
  1345.  
  1346. evt.addEventListener('onGetflvLoadStart',
  1347. $.proxy(this._onGetflvLoadStart, this));
  1348.  
  1349. evt.addEventListener('onThumbnailInfoLoadStart',
  1350. $.proxy(this._onThumbnailInfoLoadStart, this));
  1351.  
  1352. evt.addEventListener('onThumbnailInfoLoad',
  1353. $.proxy(this._onThumbnailInfoLoad, this));
  1354.  
  1355. this._initializeStoryboard();
  1356. },
  1357.  
  1358. _initializeStoryboard: function() {
  1359. this._initializeStoryboard = _.noop;
  1360.  
  1361. if (!this._storyboardModel) {
  1362. var nsv = window.NicovideoStoryboard;
  1363. this._storyboardModel = new nsv.model.StoryboardModel({
  1364. thumbnailInfo: this._thumbnailInfo,
  1365. isEnabled: this._config.get('enabled') === true,
  1366. watchId: this._watchController.getWatchId()
  1367. });
  1368. }
  1369. if (!this._storyboardView) {
  1370. this._storyboardView = new window.NicovideoStoryboard.view.StoryboardView({
  1371. watchController: this._watchController,
  1372. eventDispatcher: this._eventDispatcher,
  1373. storyboard: this._storyboardModel
  1374. });
  1375. }
  1376. },
  1377.  
  1378. load: function(watchId) {
  1379. if (watchId) {
  1380. this._storyboardModel.setWatchId(watchId);
  1381. }
  1382. this._storyboardModel.load();
  1383. },
  1384.  
  1385. unload: function() {
  1386. if (this._storyboardModel) {
  1387. this._storyboardModel.unload();
  1388. }
  1389. },
  1390.  
  1391. _onVideoInitialized: function() {
  1392. this._initializeStoryboard();
  1393. this._storyboardModel.reset();
  1394. },
  1395.  
  1396. _onWatchInfoReset: function() {
  1397. this._storyboardModel.setWatchId(this._watchController.getWatchId());
  1398. },
  1399.  
  1400. _onThumbnailInfoLoad: function(info) {
  1401. //console.log('StoryboardController._onThumbnailInfoLoad', info);
  1402.  
  1403. this._storyboardModel.update(info);
  1404. },
  1405.  
  1406. _onStoryboardSelect: function(vpos) {
  1407. //console.log('_onStoryboardSelect', vpos);
  1408. this._watchController.setVpos(vpos);
  1409. },
  1410.  
  1411. _onEnableStoryboard: function() {
  1412. window.setTimeout($.proxy(function() {
  1413. this._config.set('enabled', true);
  1414. this.load();
  1415. }, this), 0);
  1416. },
  1417.  
  1418. _onDisableStoryboard: function() {
  1419. window.setTimeout($.proxy(function() {
  1420. this._config.set('enabled', false);
  1421. this.unload();
  1422. }, this), 0);
  1423. },
  1424.  
  1425. _onGetflvLoadStart: function() {
  1426. },
  1427.  
  1428. _onThumbnailInfoLoadStart: function() {
  1429. }
  1430. });
  1431.  
  1432. return StoryboardController;
  1433. })();
  1434.  
  1435.  
  1436. window.WatchApp.mixin(window.NicovideoStoryboard, {
  1437. _addStyle: function(styles, id) {
  1438. var elm = document.createElement('style');
  1439. window.setTimeout(function() {
  1440. elm.type = 'text/css';
  1441. if (id) { elm.id = id; }
  1442.  
  1443. var text = styles.toString();
  1444. text = document.createTextNode(text);
  1445. elm.appendChild(text);
  1446. var head = document.getElementsByTagName('head');
  1447. head = head[0];
  1448. head.appendChild(elm);
  1449. }, 0);
  1450. return elm;
  1451. },
  1452. initialize: function() {
  1453. console.log('%c initialize NicovideoStoryboard', 'background: lightgreen;');
  1454. this.initializeUserConfig();
  1455.  
  1456. var root = window.WatchApp.ns;
  1457. this._playerAreaConnector =
  1458. root.init.PlayerInitializer.playerAreaConnector;
  1459. this._watchInfoModel =
  1460. root.model.WatchInfoModel.getInstance();
  1461.  
  1462. this._getflv = window.NicovideoStoryboard.api.getflv;
  1463. this._thumbnailInfo = window.NicovideoStoryboard.api.thumbnailInfo;
  1464. this._watchController = window.NicovideoStoryboard.external.watchController;
  1465.  
  1466. this._eventDispatcher = new EventDispatcher();
  1467.  
  1468. if (!this._watchController.isPremium()) {
  1469. this._watchController.popup.alert('プレミアムの機能を使っているため、一般では動きません');
  1470. return;
  1471. }
  1472.  
  1473. this._storyboardController = new window.NicovideoStoryboard.controller.StoryboardController({
  1474. thumbnailInfo: this._thumbnailInfo,
  1475. watchController: this._watchController,
  1476. eventDispatcher: this._eventDispatcher,
  1477. config: this.config
  1478. });
  1479.  
  1480. this.initializeEvent();
  1481.  
  1482. this._addStyle(__css__, 'NicovideoStoryboardCss');
  1483. },
  1484. initializeEvent: function() {
  1485. console.log('%c initializeEvent NicovideoStoryboard', 'background: lightgreen;');
  1486.  
  1487. var eventDispatcher = this._eventDispatcher;
  1488.  
  1489. this._watchInfoModel.addEventListener('reset', function() {
  1490. eventDispatcher.dispatchEvent('onWatchInfoReset');
  1491. });
  1492.  
  1493. this._playerAreaConnector.addEventListener('onVideoInitialized', function() {
  1494. eventDispatcher.dispatchEvent('onVideoInitialized');
  1495. });
  1496.  
  1497. this._getflv.addEventListener('onGetflvLoadStart', function() {
  1498. eventDispatcher.dispatchEvent('onGetflvLoadStart');
  1499. });
  1500. this._getflv.addEventListener('onGetflvLoad', function(info) {
  1501. eventDispatcher.dispatchEvent('onGetflvLoad', info);
  1502. });
  1503.  
  1504. this._thumbnailInfo.addEventListener('onThumbnailInfoLoadStart', function() {
  1505. eventDispatcher.dispatchEvent('onThumbnailInfoLoadStart');
  1506. });
  1507. this._thumbnailInfo.addEventListener('onThumbnailInfoLoad', function(info) {
  1508. eventDispatcher.dispatchEvent('onThumbnailInfoLoad', info);
  1509. });
  1510.  
  1511. },
  1512. initializeUserConfig: function() {
  1513. var prefix = 'NicoStoryboard_';
  1514. var conf = {
  1515. enabled: true,
  1516. autoScroll: true
  1517. };
  1518.  
  1519. this.config = {
  1520. get: function(key) {
  1521. try {
  1522. if (window.localStorage.hasOwnProperty(prefix + key)) {
  1523. return JSON.parse(window.localStorage.getItem(prefix + key));
  1524. }
  1525. return conf[key];
  1526. } catch (e) {
  1527. return conf[key];
  1528. }
  1529. },
  1530. set: function(key, value) {
  1531. window.localStorage.setItem(prefix + key, JSON.stringify(value));
  1532. }
  1533. };
  1534. },
  1535. load: function(watchId) {
  1536. // 動画ごとのcookieがないと取得できないので指定できてもあまり意味は無い
  1537. watchId = watchId || this._watchController.getWatchId();
  1538. this._storyboardController.load(watchId);
  1539. }
  1540.  
  1541. });
  1542.  
  1543.  
  1544. //======================================
  1545. //======================================
  1546. //======================================
  1547.  
  1548. (function() {
  1549. var watchInfoModel = window.WatchApp.ns.model.WatchInfoModel.getInstance();
  1550. if (watchInfoModel.initialized) {
  1551. console.log('%c initialize', 'background: lightgreen;');
  1552. window.NicovideoStoryboard.initialize();
  1553. } else {
  1554. var onReset = function() {
  1555. watchInfoModel.removeEventListener('reset', onReset);
  1556. window.setTimeout(function() {
  1557. watchInfoModel.removeEventListener('reset', onReset);
  1558. console.log('%c initialize', 'background: lightgreen;');
  1559. window.NicovideoStoryboard.initialize();
  1560. }, 0);
  1561. };
  1562. watchInfoModel.addEventListener('reset', onReset);
  1563. }
  1564. })();
  1565.  
  1566. };
  1567.  
  1568. var flapi = function() {
  1569. if (window.name.indexOf('getflvLoader') < 0 ) { return; }
  1570.  
  1571. var resp = document.documentElement.textContent;
  1572. var origin = 'http://' + location.host.replace(/^.*?\./, 'www.');
  1573.  
  1574. try {
  1575. parent.postMessage(JSON.stringify({
  1576. id: 'NicovideoStoryboard',
  1577. type: 'getflv',
  1578. body: {
  1579. url: location.href,
  1580. info: resp
  1581. }
  1582. }),
  1583. origin);
  1584. } catch (e) {
  1585. alert(e);
  1586. console.log('err', e);
  1587. }
  1588. };
  1589.  
  1590.  
  1591. var smileapi = function() {
  1592. if (window.name.indexOf('StoryboardLoader') < 0 ) { return; }
  1593.  
  1594. var resp = document.getElementsByTagName('smile');
  1595. var origin = 'http://' + location.host.replace(/^.*?\./, 'www.');
  1596. var xml = '';
  1597.  
  1598. if (resp.length > 0) {
  1599. xml = resp[0].outerHTML;
  1600. }
  1601.  
  1602. try {
  1603. parent.postMessage(JSON.stringify({
  1604. id: 'NicovideoStoryboard',
  1605. type: 'storyboard',
  1606. body: {
  1607. url: location.href,
  1608. xml: xml
  1609. }
  1610. }),
  1611. origin);
  1612. } catch (e) {
  1613. console.log('err', e);
  1614. }
  1615. };
  1616.  
  1617.  
  1618.  
  1619. var host = window.location.host || '';
  1620. if (host === 'flapi.nicovideo.jp') {
  1621. flapi();
  1622. } else
  1623. if (host.indexOf('smile-') >= 0) {
  1624. smileapi();
  1625. } else {
  1626. var script = document.createElement('script');
  1627. script.id = 'NicoVideoStoryboard';
  1628. script.setAttribute('type', 'text/javascript');
  1629. script.setAttribute('charset', 'UTF-8');
  1630. script.appendChild(document.createTextNode("(" + monkey + ")()"));
  1631. document.body.appendChild(script);
  1632. }
  1633.  
  1634. })();