// ==UserScript==
// @name Niconico My Theater
// @namespace knoa.jp
// @description 自分のPC内の動画ファイルと差し替えてニコニコできます。
// @include https://www.nicovideo.jp/watch/*
// @version 0.1
// @grant none
// ==/UserScript==
(function(){
const SCRIPTNAME = 'NiconicoMyTheater';
const DEBUG = false;/*
[to do]
ZenzaWatch対応。
ローディング表示
置き換え完了時に何かリアクションを。
元の動画に戻すボタンで確認してから戻せるように。
ニコニコの時刻表示に時間単位追加
[bug]
Firefoxのblob処理が重いのかどうか?
*/
if(window === top && console.time) console.time(SCRIPTNAME);
const SHIFTARROW = 1;// Shift + 左右キーで移動する(秒)
let site = {
get: {
playerOptionButton: () => $('button[data-title="設定"]'),
originalVideo: () => $('#VideoPlayer video[src]'),
videoStartButton: () => $('.VideoStartButton'),
footerContainerLinks: () => $('.FooterContainer-links'),
},
};
let originalVideo, replacedVideo;
let core = {
initialize: function(){
core.addFileButton();
core.linkVideos();
core.listenEvents();
core.addFooter();
core.addStyle();
},
addFileButton: function(){
let playerOptionButton = site.get.playerOptionButton();
if(!playerOptionButton) return setTimeout(core.addFileButton, 1000);
let fileButton = createElement(core.html.fileButton());
let input = fileButton.querySelector('input[type="file"]');
fileButton.addEventListener('click', function(e){
input.click();
});
input.addEventListener('change', function(e){
log('changed!');
let object = URL.createObjectURL(input.files[0]);
replacedVideo.src = object;
replacedVideo.classList.add('loaded');
// オリジナルビデオの状態をコピー
replacedVideo.currentTime = originalVideo.currentTime;
replacedVideo.playbackRate = originalVideo.playbackRate;
replacedVideo.volume = originalVideo.volume;
// オリジナルビデオは影の存在となる
originalVideo.style.visibility = 'hidden';//displayは上書きされる
originalVideo.muted = true;
// ローディング表示
//
});
playerOptionButton.parentNode.insertBefore(fileButton, playerOptionButton);
},
linkVideos: function(){
// リプレイスビデオ要素の準備
originalVideo = site.get.originalVideo();
if(!originalVideo) return setTimeout(core.linkVideos, 1000);
replacedVideo = createElement(core.html.replacedVideo());
originalVideo.parentNode.insertBefore(replacedVideo, originalVideo);
// 連動
originalVideo.addEventListener('play', function(e){
log('originalVideo: play!');
replacedVideo.currentTime = originalVideo.currentTime;
replacedVideo.playbackRate = originalVideo.playbackRate;
replacedVideo.play();
});
originalVideo.addEventListener('pause', function(e){
log('originalVideo: pause!');
replacedVideo.currentTime = originalVideo.currentTime;
replacedVideo.pause();
});
originalVideo.addEventListener('seeking', function(e){
log('originalVideo: seeking!');
replacedVideo.currentTime = originalVideo.currentTime;
});
originalVideo.addEventListener('canplay', function(e){
log('originalVideo: canplay!');
replacedVideo.currentTime = originalVideo.currentTime;
});
originalVideo.addEventListener('volumechange', function(e){
replacedVideo.volume = originalVideo.volume;
});
// 連動(?)
replacedVideo.addEventListener('seeking', function(e){
log('replacedVideo: seeking!');
});
replacedVideo.addEventListener('canplay', function(e){
log('replacedVideo: canplay!');
});
},
listenEvents: function(){
window.addEventListener('keydown', function(e){
switch(true){
case(e.key === 'ArrowLeft' && e.shiftKey):
replacedVideo.currentTime = originalVideo.currentTime = originalVideo.currentTime - SHIFTARROW;
break;
case(e.key === 'ArrowRight' && e.shiftKey):
replacedVideo.currentTime = originalVideo.currentTime = originalVideo.currentTime + SHIFTARROW;
break;
}
}, true);
},
addFooter: function(){
let footerContainerLinks = site.get.footerContainerLinks();
if(!footerContainerLinks) return setTimeout(core.addFooter, 1000);
footerContainerLinks.appendChild(createElement(core.html.footer()));
},
addStyle: function(){
let style = createElement(core.html.style());
document.head.appendChild(style);
},
html: {
fileButton: () => `
<button class="ActionButton ControllerButton FileButton" type="button" data-title="ファイルに差し替える">
<div class="ControllerButton-inner">
<!-- https://www.onlinewebfonts.com/icon/112309 -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" enable-background="new 0 0 1000 1000" viewBox="0 0 1000 1000" xml:space="preserve">
<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
<g><path d="M581.7,10H132.5v980h735V295.8L581.7,10z M785.8,908.3H214.2V91.7h374.5l197.1,197.1V908.3z"></path><path d="M540.8,10v326.7h326.7L540.8,10z"></path><path d="M377.5,418.3V745l285.8-163.3L377.5,418.3z"></path></g>
</svg>
</div>
<input type="file" id="${SCRIPTNAME}-file">
</button>
`,
replacedVideo: () => `
<video preload="auto" id="${SCRIPTNAME}-replaced" ${DEBUG ? 'controls' : ''}>
`,
footer: () => `
<li><a href="http://www.onlinewebfonts.com">oNline Web Fonts</a></li>
`,
style: () => `
<style type="text/css">
input#${SCRIPTNAME}-file{
display: none;
}
video#${SCRIPTNAME}-replaced{
transition: opacity .5s;
opacity: 0;
z-index: 100;
position: absolute;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
bottom: 0px;
right: 0px;
display: block;
}
video#${SCRIPTNAME}-replaced.loaded{
opacity: 1;
}
</style>
`,
},
};
let $ = function(s){return document.querySelector(s)};
let $$ = function(s){return document.querySelectorAll(s)};
let createElement = function(html){
let outer = document.createElement('div');
outer.innerHTML = html;
return outer.firstElementChild;
};
let log = function(){
if(!DEBUG) return;
let l = log.last = log.now || new Date(), n = log.now = new Date();
let stack = new Error().stack, callers = stack.match(/^([^/<]+(?=<?@))/gm) || stack.match(/[^. ]+(?= \(<anonymous)/gm) || [];
console.log(
SCRIPTNAME + ':',
/* 00:00:00.000 */ n.toLocaleTimeString() + '.' + n.getTime().toString().slice(-3),
/* +0.000s */ '+' + ((n-l)/1000).toFixed(3) + 's',
/* :00 */ ':' + stack.match(/:[0-9]+:[0-9]+/g)[1].split(':')[1],/*LINE*/
/* caller.caller */ (callers[2] ? callers[2] + '() => ' : '') +
/* caller */ (callers[1] || '') + '()',
...arguments
);
};
core.initialize();
if(window === top && console.timeEnd) console.timeEnd(SCRIPTNAME);
})();