NicoSpeedMaster

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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});


})();