NicoSpeedMaster

プレミアム会員向けに、ニコ動プレーヤーにx2.0以上の再生速度を追加します。 一般会員向けには公式と同じ速度制限があります。速度を出すには動画右クリックのメニューから「視聴方法の切替」で「http」を選んでおくとhlsより速いとされています。

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         NicoSpeedMaster
// @namespace    https://toogiri.buhoho.net/
// @version      0.1.0.1
// @description  プレミアム会員向けに、ニコ動プレーヤーにx2.0以上の再生速度を追加します。 一般会員向けには公式と同じ速度制限があります。速度を出すには動画右クリックのメニューから「視聴方法の切替」で「http」を選んでおくとhlsより速いとされています。
// @author       buhoho
// @match        https://*.nicovideo.jp/watch/*
// @grant        none
// @license      MIT
// ==/UserScript==



(function () {

'use strict';

// スクリプトが管理している再生速度
let customPlaybackRate = parseFloat(localStorage.customPlaybackRate ?? 1.0);

// 変更前のvideo要素
let prevVideo, prevVideoSrc;

// 多分Premiumじゃないとろくに再生速度出ないので、公式仕様通り制限
let isPremium = JSON.parse(document.querySelector('#CommonHeader').dataset.commonHeader).initConfig.user.isPremium;


function createPlaybackRateMenuItem(rate) {
	const menuItem = document.createElement('div');
	menuItem.classList.add('PlaybackRateMenuItem');
	menuItem.innerHTML = rate === customPlaybackRate?
		`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="CheckIcon PlaybackRateMenuItem-checkIcon"><path d="M13.7 29.9 2.9 19.1l4.2-4.3 6.6 6.6L28.9 6.2l4.2 4.3-19.4 19.4z"></path></svg><div>x${rate.toFixed(1)}</div>`:
		`<div class="PlaybackRateMenuItem-iconSpace"></div><div>x${rate.toFixed(1)}</div>`;
	menuItem.onclick = function () {
		localStorage.customPlaybackRate = prevVideo.playbackRate = customPlaybackRate = rate;
		togglePlaybackRateMenu();
	};
	return menuItem;
}

function createPlaybackRateMenu() {
	const menu = document.createElement('div');
	menu.classList.add('PlaybackRateMenu');
	menu.innerHTML = '<div class="PlaybackRateMenu-title">再生速度</div>';

	const menuContents = document.createElement('div');
	menuContents.classList.add('PlaybackRateMenu-contents');
	menu.appendChild(menuContents);

	const rates = isPremium ? [0.5, 1.0, 1.5, 1.8, 2.3, 2.7, 3.4, 4.2]: [0.5, 1.0, 1.25];
	for (const rate of rates) {
		const menuItem = createPlaybackRateMenuItem(rate);
		menuContents.appendChild(menuItem);
	}

	return menu;
}

function togglePlaybackRateMenu(e) {
	e && e.stopPropagation();
	const existingMenu = document.querySelector('.PlaybackRateMenu');
	if (existingMenu) {
		existingMenu.remove();
	} else {
		const menu = createPlaybackRateMenu();
		const container = document.querySelector('.VideoOverlayContainer');
		if (container) {
			container.appendChild(menu);
		}
	}
}

function buttonShowRate(rate) {
	let btn = document.querySelector('.ActionButton.PlaybackRateButton');

	const btnItem = document.createElement('div');
	//btnItem.style.border = '2px solid white';
	btnItem.style.setProperty('border', '2px solid white', 'important');
	// btnItem.style.background = '#113';
	btnItem.style.padding = '2px';
	btnItem.style.borderRadius = '12px';
	// btnItem.style.backgroundColor = 'rgb(20, 13, 55)';
	btnItem.innerText = `x${rate.toFixed(1)}`;

	// 文字色を白に設定
	btn.style.color = 'white';
	// btn.style.backgroundColor = 'rgb(20, 13, 55)';

	if (rate > 1.0 && rate <= 2.0) {
		btnItem.style.backgroundColor = '#002176';
		btnItem.style.setProperty('border', '2px solid #aaeeff', 'important');
		btnItem.style.setProperty('color', '#aaeeff', 'important');
		// btnItem.style.backgroundColor = '#003386'; // 濃い青色
	} else if (rate > 2.0) {
		btnItem.style.backgroundColor = '#660005';
		btnItem.style.setProperty('border', '2px solid #ffaacf', 'important');
		btnItem.style.setProperty('color', '#ffaacf', 'important');
		// btnItem.style.backgroundColor = '#a60012'; // 濃い赤色
	}

	btn.innerHTML = '';
	btn.appendChild(btnItem);
}

function setPlaybackRateEventListener(v) {
	v.addEventListener('ratechange', () => {
		// console.log("レートが変更されました。");
		let rate = v.playbackRate;
		if (rate !== customPlaybackRate) {
			// 再生速度がスクリプトが管理しているものと異なる場合、管理している再生速度に戻す
			v.playbackRate = customPlaybackRate;
			return;
		}
		buttonShowRate(rate);
	});
}

new MutationObserver(mutations => {
	const v = document.querySelector('#MainVideoPlayer video');
	if (prevVideo === v && prevVideoSrc === v.src)
		return;
	// video 要素が変更されたときの処理を記述
	setPlaybackRateEventListener(v);
	// メニュー表示処理を上書き
	document.querySelector('button.ActionButton.PlaybackRateButton').onclick = togglePlaybackRateMenu;
	// 速度更新
	v.playbackRate = customPlaybackRate;
	// 現在のvideo要素を保存(変更を検知するため)
	prevVideo = v;
	prevVideoSrc = v.src;
}).observe(document.querySelector('#js-app'), {childList: true, subtree: true});


})();