// ==UserScript==
// @name 中国大学慕课icourse163最大音量放大
// @namespace http://tampermonkey.net/
// @version 0.3
// @description 中国大学生慕课icourse163最大音量放大,可以将音量调整成n倍
// @author Re-Hao
// @match https://www.icourse163.org/learn/*
// @grant none
// @downloadURL https://update.greasyfork.icu/scripts/418884/%E4%B8%AD%E5%9B%BD%E5%A4%A7%E5%AD%A6%E6%85%95%E8%AF%BEicourse163%E6%9C%80%E5%A4%A7%E9%9F%B3%E9%87%8F%E6%94%BE%E5%A4%A7.user.js
// @updateURL https://update.greasyfork.icu/scripts/418884/%E4%B8%AD%E5%9B%BD%E5%A4%A7%E5%AD%A6%E6%85%95%E8%AF%BEicourse163%E6%9C%80%E5%A4%A7%E9%9F%B3%E9%87%8F%E6%94%BE%E5%A4%A7.meta.js
// ==/UserScript==
/*
v0.3版本说明
修复了导致页面卡死的bug
v0.2版本说明
内部版本, 修复了部分bug以及版本说明
v0.1版本说明
当从视频界面切换到其他页面时, 会由于video检测处的do-while死循环问题导致页面内存泄露卡死.
*/
(function() {
'use strict';
// 脚本全局变量
window._rh = {};
// 初始化音量的默认值
function initVideoVolumnValue() {
window.localStorage.setItem("rhdata", JSON.stringify({
volumn: 6.0
}));
}
// 判断是否匹配界面
const matchPage = () => {return /#\/learn\/content\?type=detail.*/i.test(location.hash)};
// 视频转换器的interval
let converterInterval = undefined;
// 转换器的运行状态
let videoVolumnConverterRunningStatus = false;
// 设置默认的音量值, 在localstorage中添加脚本变量
if (!window.localStorage.getItem('rhdata')) {
initVideoVolumnValue();
}
// 核心函数, 转换视频的音量, 返回一个自构造的包含AudioContext对象, 后续对视频最大音量修改可以使用返回值
function amplifyMedia(mediaElem, multiplier) {
var context = new(window.AudioContext || window.webkitAudioContext),
result = {
context: context,
source: context.createMediaElementSource(mediaElem),
gain: context.createGain(),
media: mediaElem,
amplify: function(multiplier) {
result.gain.gain.value = multiplier;
},
getAmpLevel: function() {
return result.gain.gain.value;
}
};
result.source.connect(result.gain);
result.gain.connect(context.destination);
result.amplify(multiplier);
return result;
}
// 由于icourse163为单页面应用, 并且使用了React Router Hash History模式
// Tampermonkey的@mathch关键字不支持匹配'#'后的字符串,因此需要在脚本内判断
// 此处设置一个定时器, 每3秒检测一次当前页面的路径是否匹配目标链接
const _begin = setInterval(() => {
//console.log('开始检查页面路径');
// 匹配视频界面
if (matchPage()) {
//console.log('页面路径匹配');
// 开始视频音量转换
startVideoVolumnConverter();
} else {
//console.log('页面路径不匹配');
// 停止视频音量转换
stopVideoVolumnConverter();
return;
}
}, 3000);
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
// 开启视频音量转换器
function startVideoVolumnConverter() {
if (videoVolumnConverterRunningStatus) {
//console.log('单例函数startVideoVolumnConverter重复运行, 直接退出');
return;
}
//console.log('******* 单例函数startVideoVolumnConverter启动了 *********');
// 将运行标志位设置为true
videoVolumnConverterRunningStatus = true;
if ( !document.getElementById("rh-container") ) {
// 视频顶部导航栏
const volumnContainer = document.getElementsByClassName('titleBar')[0];
// 音量设置输入框以及修改按钮的容器
const volumnBox = document.createElement('div');
// TODO
// 目前对输入框输入的内容没有检查
volumnBox.innerHTML =
'' +
``;
volumnBox.id = 'rh-container';
volumnContainer.appendChild(volumnBox);
// rh-container中的音量输入框对象
const volumnInput = document.getElementById("rh-volumn");
// 将音量输入框的值设置为localstorage中的值
volumnInput.value = JSON.parse(window.localStorage.getItem('rhdata')).volumn;
}
// 正式启动脚本
let video = undefined;
let playButton = undefined;
let videoVolumn = undefined;
// 开始循检测网页中的视频, 使用async-await模式放置死循环
converterInterval = setInterval(async () => {
do {
//console.log('video循环');
// 如果页面不匹配, 则直接退出本次循环
if (!matchPage()) {
//console.log('--------- 由于页面不匹配直接跳出video循环');
return;
}
await sleep(500);
video = document.getElementsByTagName('video')[0];
// 当video标签不存在, 一直循环检测Video
} while ( !video )
//console.log('音量解析运行');
// 尝试解析音量值
try {
videoVolumn = parseFloat('' + JSON.parse(window.localStorage.getItem('rhdata')).volumn)
} catch {
// 初始化音量值
initVideoVolumnValue();
videoVolumn = 6.0;
}
// 判断视频还是不是之前保存的视频, 如果不是的话就更新视频以及音量值
if (window._rh.video !== video) {
//console.log('找到了新的视频, 开始进行音量转换');
window._rh.video = video;
let result = amplifyMedia(video, videoVolumn);
window._rh.result = result;
} else {
//console.log('还是旧的视频, 什么都不做, 将在1秒后进行下一轮检查');
// None
}
}, 1000);
}
// 停止视频转换
function stopVideoVolumnConverter() {
clearInterval(converterInterval);
// 将运行标志位设置为false
videoVolumnConverterRunningStatus = false;
}
})();