Youtube HTML5 Karaoke

HTML5 Karaoke Vocal Control, support center channel cut on regular MV, left/right channel vocal/instrumental mixed MVs. Support: Youtube and Bilibili

  1. // ==UserScript==
  2. // @name Youtube HTML5 Karaoke
  3. // @namespace https://github.com/heyqule/youtubekaraoke
  4. // @version 1.4.0
  5. // @description HTML5 Karaoke Vocal Control, support center channel cut on regular MV, left/right channel vocal/instrumental mixed MVs. Support: Youtube and Bilibili
  6. // @description:zh HTML5 卡拉OK人声控制,支持常规MV中置声道切换,左右声道人声/器乐混合MV。支持:Youtube 和 Bilibili
  7. // @description:ja HTML5 カラオケ ボーカル コントロール、通常の MV でのセンター チャンネル カット、左/右チャンネルのボーカル/インストゥルメンタル ミックス MV をサポート。サポート: Youtube, Bilibili
  8. // @author heyqule
  9. // @license GPLv3
  10. // @match https://www.youtube.com/*
  11. // @match https://www.bilibili.com/*
  12. // @require https://code.jquery.com/jquery-4.0.0-beta.min.js
  13. // @require https://cdn.jsdelivr.net/npm/js-md5@0.7.3/build/md5.min.js
  14. // @grant unsafeWindow
  15. // @grant GM.xmlHttpRequest
  16. // @grant window.onurlchange
  17. // @run-at document-end
  18. // ==/UserScript==
  19.  
  20. (function($, md5) {
  21. 'use strict';
  22.  
  23.  
  24. if (window.trustedTypes && window.trustedTypes.createPolicy) {
  25. window.trustedTypes.createPolicy('default', {
  26. createHTML: (string) => string,
  27. createScript: (string) => string
  28. });
  29. }
  30.  
  31. const languages={
  32. "zh":{
  33. "title": "🎤 控制",
  34. "off": "🎤: 关",
  35. "on": "🎤: 开",
  36. "vocal_l1": "人声衰减",
  37. "vocal_l2": "(左 - 中1 - 中2 - 右)",
  38. "high_pass": "高通",
  39. "low_pass": "低通",
  40. "mic_gain": "🎤 增益",
  41. "mic_gain_desc": "从浏览器连接的麦克风有明显的延迟。 建议通过音频接口器去控制。",
  42. },
  43. //Ja by google translate
  44. "ja":{
  45. "title": "🎤 コントロール",
  46. "off": "🎤: オフ",
  47. "on": "🎤: オン",
  48. "vocal_l1": "ボーカルの減衰",
  49. "vocal_l2": "(左 - 中1 - 中2 - 右)",
  50. "high_pass": "ハイパス",
  51. "low_pass": "ローパス",
  52. "mic_gain": "🎤 ゲイン",
  53. "mic_gain_desc": "ブラウザから接続したマイクの遅延が顕著です。 オーディオインターフェース経由でコントロールすることをお勧めします。",
  54. },
  55. "en":{
  56. "title": "🎤 Controls",
  57. "off": "🎤: OFF",
  58. "on": "🎤: ON",
  59. "vocal_l1": "Vocal Attenuation",
  60. "vocal_l2": "(left - center1 - center2 - right)",
  61. "high_pass": "High Pass",
  62. "low_pass": "Low Pass",
  63. "mic_gain": "🎤 Gain",
  64. "mic_gain_desc": "Mic connected from browser has noticeable delay. Recommend to connect mic through an audio interface.",
  65. },
  66. }
  67. let lang = 'en';
  68.  
  69.  
  70. //Youtube Handler
  71. let mediaElement = 'video.html5-main-video';
  72. let targetContainer = 'div.ytp-right-controls';
  73. let UiAttachTo = 'div#primary div#player';
  74. let youtubeDarkThemeUiAttachTo = 'div#primary div#alerts';
  75. let buttonTag = '<button />';
  76. let buttonClass = 'ytp-karaoke-button ytp-button';
  77. let buttonStyle = 'position: relative; top:-1.5rem; padding-left:1rem; font-size:2rem; cursor: pointer;';
  78. let urlChangePattern = 'watch';
  79. let getSongId = function() {
  80. let queryString = window.location.search;
  81. let urlParams = new URLSearchParams(queryString);
  82. return urlParams.get('v');
  83. }
  84. let isYoutubeDarkTheme = document.documentElement.hasAttribute('dark');
  85. let darkThemeTextColor = ' color:#fff;';
  86.  
  87. let youtubeLang = document.documentElement.getAttribute('lang');
  88. if (youtubeLang)
  89. {
  90. lang = (youtubeLang.indexOf("-") != -1 ? youtubeLang.split("-")[0] : 'en').toLocaleLowerCase();
  91. }
  92.  
  93. if (/bilibili\.com/.test(window.location.href)) {
  94. mediaElement = '#bilibili-player video';
  95. targetContainer = 'div.bpx-player-control-bottom-right';
  96. UiAttachTo = '#playerWrap';
  97. buttonTag = '<div />';
  98. buttonClass = 'bpx-player-ctrl-btn';
  99. buttonStyle = 'position: relative; margin-right:1rem; font-size:1.5rem; cursor: pointer;';
  100. urlChangePattern = 'video';
  101. getSongId = function() {
  102. let token = window.location.pathname;
  103. return md5(token);
  104. }
  105.  
  106. if (/bilibili\.com\/bangumi\/play/.test(window.location.href)) {
  107. targetContainer = 'div.bpx-player-control-bottom-right';
  108. UiAttachTo = '#bilibili-player-wrap';
  109. urlChangePattern = 'bangumi/play';
  110. }
  111. }
  112.  
  113.  
  114. let KaraokeUI = function ($) {
  115. let _translate = function(label) {
  116. return languages[lang][label] ?? languages["en"][label] ?? '{404 locale:'+label+'}';
  117. }
  118. let karaokeButton = $(buttonTag,{
  119. title: _translate('off'),
  120. id: 'karaoke-button',
  121. class: buttonClass,
  122. text: '🎤',
  123. style: buttonStyle,
  124. 'aria-haspopup': 'true',
  125. onClick: 'KaraokePluginSwitch();'
  126. });
  127. //Control Panel
  128. let controlPanel, channelAdjustControl, highPassAdjustControl, lowPassAdjustControl, gainAdjustControl;
  129. let highPassAdjustDisplay, lowPassAdjustDisplay
  130.  
  131. return {
  132. menuUI : function() {
  133. $(targetContainer).prepend(karaokeButton);
  134. },
  135. controlPanelUI : function(channelAdjustedValue, highPassAdjustedValue, lowPassAdjustedValue, gainAdjustedValue) {
  136.  
  137. let columnStyle = 'width:33%; display:inline-block;';
  138. let titleStyle = '';
  139. if (isYoutubeDarkTheme) {
  140. columnStyle += darkThemeTextColor;
  141. titleStyle = darkThemeTextColor;
  142. }
  143.  
  144. controlPanel = $('<div>',{
  145. id:"karaoke_controlpanel",
  146. });
  147.  
  148. controlPanel.append($('<h3>',{
  149. text: _translate('title'),
  150. style: titleStyle
  151. }));
  152.  
  153. channelAdjustControl = $('<input>',{
  154. type: 'range',
  155. id: 'channelshift',
  156. min: 0,
  157. max: 3,
  158. value: channelAdjustedValue,
  159. step: 1,
  160. onchange: 'KaraokePluginChannelAdjust(this)'
  161. });
  162. highPassAdjustControl = $('<input>',{
  163. type: 'range',
  164. id: 'highpass',
  165. min: 50,
  166. max: 400,
  167. value: highPassAdjustedValue,
  168. step: 10,
  169. onchange: 'KaraokePluginHighPassAdjust(this)'
  170. });
  171. lowPassAdjustControl = $('<input>',{
  172. type: 'range',
  173. id: 'lowpass',
  174. min: 2000,
  175. max: 8000,
  176. value: lowPassAdjustedValue,
  177. step: 200,
  178. onchange: 'KaraokePluginLowPassAdjust(this)'
  179. })
  180. gainAdjustControl = $('<input>',{
  181. type: 'range',
  182. id: 'micgain',
  183. min: 0,
  184. max: 2,
  185. value: gainAdjustedValue,
  186. step: 0.1,
  187. onchange: 'KaraokePluginMicGainAdjust(this)'
  188. })
  189.  
  190. controlPanel.append(
  191. $('<div>',{style: columnStyle}).
  192. append('<label style="width:100px;">'+_translate('vocal_l1')+':</label><br />').
  193. append('<label>'+_translate('vocal_l2')+'</label><br />').
  194. append(channelAdjustControl).
  195. append('<br />').
  196. append('<label style="width:100px;">'+_translate('high_pass')+': <span id="KaraokeHighPassValue">'+highPassAdjustedValue+'</span> Hz</label><br />').
  197. append(highPassAdjustControl).
  198. append('<br />').
  199. append('<label style="width:100px;">'+_translate('low_pass')+': <span id="KaraokeLowPassValue">'+lowPassAdjustedValue+'</span> Hz</label><br />').
  200. append(lowPassAdjustControl)
  201. );
  202.  
  203.  
  204. let secondColumn = $('<div>',{style: columnStyle});
  205.  
  206. secondColumn.append('<label style="width:100px;">'+_translate('mic_gain')+': <span id="KaraokeGainValue">'+gainAdjustedValue+'</span></label><br />').
  207. append(gainAdjustControl).
  208. append('<p>'+_translate('mic_gain_desc')+'</p>');
  209.  
  210. controlPanel.append(secondColumn);
  211.  
  212. if (isYoutubeDarkTheme) {
  213. controlPanel.insertBefore(youtubeDarkThemeUiAttachTo);
  214. }
  215. else
  216. {
  217. controlPanel.insertAfter(UiAttachTo);
  218. }
  219.  
  220. highPassAdjustDisplay = $('#KaraokeHighPassValue');
  221. lowPassAdjustDisplay = $('#KaraokeLowPassValue');
  222.  
  223. return controlPanel
  224. },
  225. setKaraokeButtonOn: function() {
  226. karaokeButton.attr('title', _translate('on'));
  227. },
  228. setKaraokeButtonOff: function() {
  229. karaokeButton.attr('title',_translate('off'));
  230. },
  231. getChannelAdjustControl: function() {
  232. return channelAdjustControl
  233. },
  234. getHighPassAdjustControl: function() {
  235. return highPassAdjustControl
  236. },
  237. getLowPassAdjustControl: function() {
  238. return lowPassAdjustControl
  239. },
  240. getHighPassAdjustDisplay: function() {
  241. return highPassAdjustDisplay;
  242. },
  243. getLowPassAdjustDisplay: function() {
  244. return lowPassAdjustDisplay;
  245. }
  246. }
  247. }(jQuery)
  248.  
  249. let KaraokePlugin = function ($, KaraokeUI) {
  250.  
  251. const MAX_CACHE_SIZE = 5000;
  252. const MAX_RETRIES = 20;
  253. const TIME_INTERVAL = 1500;
  254. //webaudio elements
  255. let audioContext, audioSource,micAudioContext, micSource;
  256. let karaokeFilterOn = false;
  257. let channelAdjustedValue = 1, gainAdjustedValue = 1;
  258. let highPassAdjustedValue = 200, lowPassAdjustedValue = 6000
  259. let trackSearchDialog = null;
  260.  
  261. let _createBiquadFilter = function(type,freq,qValue)
  262. {
  263. let filter = audioContext.createBiquadFilter();
  264. filter.type = type;
  265. filter.frequency.value = freq;
  266. filter.Q.value = qValue;
  267. return filter;
  268. }
  269.  
  270. /**
  271. * Cut common vocal frequencies @ center
  272. * Algo origin: https://github.com/stanton119/YouTube-Karaoke
  273. */
  274. let _cutCenterV1 = function()
  275. {
  276. //cutoff frequencies
  277. let f1 = highPassAdjustedValue;
  278. let f2 = lowPassAdjustedValue;
  279. console.log('setting center cut v1 @'+f1+' - '+f2);
  280. //splitter and gains
  281. let splitter, gainL, gainR;
  282. //biquadFilters
  283. let filterLP1, filterHP1, filterLP2, filterHP2;
  284. let filterLP3, filterHP3, filterLP4, filterHP4;
  285. //phase inversion filter
  286. splitter = audioContext.createChannelSplitter(2);
  287. gainL = audioContext.createGain();
  288. gainR = audioContext.createGain();
  289. gainL.gain.value = 1;
  290. gainR.gain.value = -1;
  291. splitter.connect(gainL, 0);
  292. splitter.connect(gainR, 1);
  293. gainL.connect(audioContext.destination);
  294. gainR.connect(audioContext.destination);
  295. //biquad filters
  296. filterLP1 = _createBiquadFilter("lowpass",f2,1);
  297. filterLP2 = _createBiquadFilter("lowpass",f1,1);
  298. filterLP3 = _createBiquadFilter("lowpass",f2,1);
  299. filterLP4 = _createBiquadFilter("lowpass",f1,1);
  300.  
  301. filterHP1 = _createBiquadFilter("highpass",f1,1);
  302. filterHP2 = _createBiquadFilter("highpass",f2,1);
  303. filterHP3 = _createBiquadFilter("highpass",f1,1);
  304. filterHP4 = _createBiquadFilter("highpass",f2,1);
  305. //connect filters
  306. audioSource.connect(filterLP1);
  307. audioSource.connect(filterLP2);
  308. audioSource.connect(filterHP2);
  309. filterLP1.connect(filterLP3);
  310. filterLP3.connect(filterHP1);
  311. filterHP1.connect(filterHP3);
  312. filterHP3.connect(splitter);
  313. filterLP2.connect(filterLP4);
  314. filterLP4.connect(audioContext.destination);
  315. filterHP2.connect(filterHP4);
  316. filterHP4.connect(audioContext.destination);
  317. }
  318.  
  319. /**
  320. * Cut common vocal frequencies @ center with preserve stereo field
  321. * Algo origin: https://github.com/stanton119/YouTube-Karaoke
  322. */
  323. let _cutCenterV2 = function()
  324. {
  325. //cutoff frequencies
  326. let f1 = highPassAdjustedValue;
  327. let f2 = lowPassAdjustedValue;
  328.  
  329. console.log('setting center cut with stereo field @'+f1+' - '+f2);
  330. // stereo conversion
  331. let merger = audioContext.createChannelMerger(2);
  332. merger.connect(audioContext.destination);
  333.  
  334. // L_Out = (Mid+side)/2
  335. let gainNodeMS1_05 = audioContext.createGain();
  336. gainNodeMS1_05.gain.value = 0.5;
  337. gainNodeMS1_05.connect(merger,0,0);
  338.  
  339. // R_Out = (Mid-side)/2
  340. let gainNodeMS2_05 = audioContext.createGain();
  341. gainNodeMS2_05.gain.value = 0.5;
  342. gainNodeMS2_05.connect(merger,0,1);
  343.  
  344. let gainNodeS_1 = audioContext.createGain();
  345. gainNodeS_1.gain.value = -1;
  346. gainNodeS_1.connect(gainNodeMS2_05);
  347.  
  348. // create band stop filter using two cascaded biquads
  349. // inputs -> FilterLP1 & FilterLP2
  350. // outputs -> splitter & destinations
  351.  
  352. // Bandstop filter = LP + HP
  353. let FilterLP1 = _createBiquadFilter('lowpass', f1, 1);
  354. let FilterLP2 = _createBiquadFilter('lowpass', f1, 1);
  355. FilterLP1.connect(FilterLP2);
  356.  
  357. let FilterHP1 = _createBiquadFilter('highpass', f2, 1);
  358. let FilterHP2 = _createBiquadFilter('highpass', f2, 1);
  359. FilterHP1.connect(FilterHP2);
  360.  
  361. // connect filters to left and right outputs
  362. FilterLP2.connect(gainNodeMS1_05);
  363. FilterHP2.connect(gainNodeMS1_05);
  364. FilterLP2.connect(gainNodeMS2_05);
  365. FilterHP2.connect(gainNodeMS2_05);
  366.  
  367. // band pass with gain, adds mids into the side channel
  368. let gainNodeBP = audioContext.createGain();
  369. gainNodeBP.gain.value = 1;
  370. let FilterBP1 = _createBiquadFilter('lowpass', f2, 1);
  371. let FilterBP2 = _createBiquadFilter('lowpass', f2, 1);
  372. FilterBP2.connect(FilterBP1);
  373.  
  374. let FilterBP3 = _createBiquadFilter('highpass', f1, 1);
  375. FilterBP3.connect(FilterBP2);
  376.  
  377. let FilterBP4 = _createBiquadFilter('highpass', f1, 1);
  378. FilterBP4.connect(FilterBP3);
  379.  
  380. FilterBP1.connect(gainNodeBP);
  381. gainNodeBP.connect(gainNodeS_1);
  382. gainNodeBP.connect(gainNodeMS1_05);
  383.  
  384. // mid-side conversion
  385. // split into L/R
  386. let splitter = audioContext.createChannelSplitter(2);
  387. // mid = L+R
  388. splitter.connect(FilterLP1,0); // // L->filter
  389. splitter.connect(FilterHP1,0);
  390. splitter.connect(FilterLP1,1); // R->filter
  391. splitter.connect(FilterHP1,1);
  392.  
  393. // side = L-R, 2 outputs, 2 destinations
  394. let gainNodeR_1 = audioContext.createGain();
  395. gainNodeR_1.gain.value = -1;
  396. splitter.connect(gainNodeR_1,1);
  397.  
  398. gainNodeR_1.connect(gainNodeS_1);
  399. splitter.connect(gainNodeS_1,0);
  400. gainNodeR_1.connect(gainNodeMS1_05);
  401. splitter.connect(gainNodeMS1_05,0);
  402.  
  403. gainNodeR_1.connect(FilterBP4);
  404. splitter.connect(FilterBP4,0);
  405. audioSource.connect(splitter);
  406. }
  407.  
  408. /**
  409. * Expand left channel to both channel, drop right channel
  410. */
  411. let _cutRight = function()
  412. {
  413. console.log('setting right cut');
  414. let splitter, merger;
  415. splitter = audioContext.createChannelSplitter(2);
  416. merger = audioContext.createChannelMerger(1);
  417. splitter.connect(merger, 0);
  418. audioSource.connect(splitter);
  419. merger.connect(audioContext.destination);
  420. }
  421.  
  422. /**
  423. * Expand right channel to both channel, drop left channel
  424. */
  425. let _cutLeft = function()
  426. {
  427. console.log('setting left cut');
  428. let splitter,merger;
  429. splitter = audioContext.createChannelSplitter(2);
  430. merger = audioContext.createChannelMerger(1);
  431. splitter.connect(merger, 1);
  432. audioSource.connect(splitter);
  433. merger.connect(audioContext.destination);
  434. }
  435.  
  436. /**
  437. * Handle Microphone gain. This only applicable to mic that connected to browser.
  438. * @param amount
  439. * @private
  440. */
  441. let _micGain = function(amount)
  442. {
  443. let gainElement = $('#KaraokeGainValue')
  444. gainElement.html(amount);
  445. console.log(gainElement.html());
  446.  
  447. micSource.disconnect();
  448.  
  449. let micGain = micAudioContext.createGain();
  450. micSource.connect( micGain );
  451. micGain.connect( micAudioContext.destination );
  452. micGain.gain.value = amount;
  453. micSource.connect( micAudioContext.destination );
  454. }
  455.  
  456. /**
  457. * 0 = left cut, 1 = center cut v2, 2 = center cut v1, 2 = right cut
  458. **/
  459. let _adjustChannel = function()
  460. {
  461. console.log('channelAdjust:'+channelAdjustedValue);
  462. _disconnectProcessors();
  463. switch(channelAdjustedValue) {
  464. case 0:
  465. _cutLeft();
  466. break;
  467. case 1:
  468. _cutCenterV2();
  469. break;
  470. case 2:
  471. _cutCenterV1();
  472. break;
  473. case 3:
  474. _cutRight();
  475. break;
  476. }
  477.  
  478. _saveSetting();
  479. }
  480.  
  481. let _disconnectProcessors = function() {
  482. console.log('disconnect audio processors');
  483. audioSource.disconnect();
  484. }
  485.  
  486. let _getSongId = function() {
  487. return getSongId();
  488. }
  489.  
  490. let _loadSetting = function() {
  491. let songId = _getSongId();
  492. if(typeof songId === undefined || songId === null) {
  493. return;
  494. }
  495. let localSetting = localStorage.getItem(songId);
  496. let savedItem = null;
  497. if(localSetting !== null) {
  498. savedItem = JSON.parse(localSetting);
  499. }
  500. console.log("Loading "+songId, savedItem);
  501. if(savedItem !== null) {
  502. touchLocalStorage(songId, savedItem);
  503. }
  504. }
  505.  
  506. let touchLocalStorage = function(songId, savedItem) {
  507. channelAdjustedValue = savedItem.cv;
  508. lowPassAdjustedValue = savedItem.lpv;
  509. highPassAdjustedValue = savedItem.hpv;
  510.  
  511. savedItem.date = Date.now();
  512. localStorage.setItem(songId, JSON.stringify(savedItem));
  513. }
  514.  
  515. let _readjustControls = function() {
  516. KaraokeUI.getChannelAdjustControl().val(channelAdjustedValue);
  517. KaraokeUI.getHighPassAdjustControl().val(highPassAdjustedValue);
  518. KaraokeUI.getLowPassAdjustControl().val(lowPassAdjustedValue);
  519. KaraokeUI.getHighPassAdjustDisplay().html(highPassAdjustedValue.toString())
  520. KaraokeUI.getLowPassAdjustDisplay().html(lowPassAdjustedValue.toString())
  521. }
  522.  
  523. let _saveSetting = function() {
  524. let songId = _getSongId();
  525. if(songId === null) {
  526. return;
  527. }
  528. let data = {
  529. cv: channelAdjustedValue,
  530. lpv: lowPassAdjustedValue,
  531. hpv: highPassAdjustedValue,
  532. date: Date.now()
  533. }
  534. console.log('Saving Setting: '+songId, data)
  535. localStorage.setItem(songId, JSON.stringify(data));
  536.  
  537. _trimCache();
  538. }
  539.  
  540. let _trimCache = function() {
  541. if(localStorage.length > MAX_CACHE_SIZE) {
  542. let sortableArray = [];
  543. for (let i = 0; i < localStorage.length; i++) {
  544. let jsonItem = localStorage.getItem(localStorage.key(i));
  545. let item = JSON.parse(jsonItem);
  546. if(typeof item.cv !== undefined)
  547. {
  548. sortableArray[localStorage.key(i)] = {
  549. key: localStorage.key(i),
  550. data: JSON.parse(localStorage.getItem(localStorage.key(i)))
  551. };
  552. }
  553. }
  554. sortableArray.sort((a, b) => (a.data.date > b.data.date) ? 1 : -1);
  555. for (let i = 0; i < MAX_CACHE_SIZE/5; i++) {
  556. localStorage.removeItem(sortableArray[i].key);
  557. }
  558. }
  559. }
  560.  
  561. let _connectAudio = function(element) {
  562. //setup audio routing
  563. try {
  564. window.AudioContext = window.AudioContext || window.webkitAudioContext;
  565. audioContext = new AudioContext();
  566. audioSource = audioContext.createMediaElementSource(element);
  567. audioSource.connect(audioContext.destination);
  568. } catch (e) {
  569. console.error('Media element not found.');
  570. console.error(e.message);
  571. }
  572. }
  573.  
  574. let _getVideoElement = function(mediaElement) {
  575. let element = $(mediaElement)
  576. if (typeof $(mediaElement)[0] !== 'undefined') {
  577. element = $(mediaElement)[0]
  578. }
  579. return element;
  580. }
  581.  
  582. return {
  583. setupAudioSource : function ()
  584. {
  585. if(typeof _getVideoElement(mediaElement).tagName === 'undefined')
  586. {
  587. console.log('audio connecting via interval');
  588. var retries = 0;
  589. var intervalId = setInterval(function() {
  590. console.log('audio connect retry: '+retries);
  591. if(retries > 10) {
  592. clearInterval(intervalId);
  593. return this;
  594. }
  595. console.log(_getVideoElement(mediaElement));
  596. if(_getVideoElement(mediaElement).tagName === 'VIDEO') {
  597. console.log('audio connected');
  598. _connectAudio(_getVideoElement(mediaElement));
  599. clearInterval(intervalId);
  600. return this;
  601. }
  602. retries++;
  603. }, TIME_INTERVAL);
  604. }
  605. else
  606. {
  607. console.log('audio connected immediately');
  608. _connectAudio(_getVideoElement(mediaElement));
  609. }
  610. return this;
  611. },
  612. setupMic: function() {
  613. navigator.mediaDevices.getUserMedia({ audio: true })
  614. .then(function(stream) {
  615. /* use the stream */
  616. window.AudioContext = window.AudioContext || window.webkitAudioContext;
  617. micAudioContext = new AudioContext();
  618. console.log('Mic Latency:'+micAudioContext.baseLatency);
  619.  
  620. // Create an AudioNode from the stream.
  621. micSource = micAudioContext.createMediaStreamSource( stream );
  622.  
  623. // Connect it to the destination to hear yourself (or any other node for processing!)
  624. micSource.connect( micAudioContext.destination );
  625. })
  626. .catch(function(err) {
  627. /* handle the error */
  628. });
  629.  
  630. return this;
  631. },
  632. setupMenu: function()
  633. {
  634. if($(targetContainer).length === 0)
  635. {
  636. console.log('menu connecting via interval');
  637. var retries = 0;
  638. var intervalId = setInterval(function() {
  639. console.log('menu retry: '+retries);
  640. if(retries > 10) {
  641. clearInterval(intervalId);
  642. return this;
  643. }
  644. if($(targetContainer).length > 0) {
  645. console.log('audio connected');
  646. KaraokeUI.menuUI();
  647. clearInterval(intervalId);
  648. return this;
  649. }
  650. retries++;
  651. }, TIME_INTERVAL);
  652. }
  653. else
  654. {
  655. console.log('menu connected immediately');
  656. KaraokeUI.menuUI();
  657. }
  658. },
  659. filterOn: function() {
  660. console.log("Removing vocals");
  661. _adjustChannel();
  662. return this;
  663. },
  664. filterOff: function() {
  665. console.log("Adding in vocals");
  666. _disconnectProcessors();
  667. audioSource.connect(audioContext.destination);
  668. return this;
  669. },
  670. switch: function()
  671. {
  672. if(karaokeFilterOn)
  673. {
  674. karaokeFilterOn = false;
  675. this.filterOff();
  676. KaraokeUI.setKaraokeButtonOff();
  677. this.removeControlPanel();
  678. }
  679. else
  680. {
  681. karaokeFilterOn = true;
  682. this.filterOn();
  683. KaraokeUI.setKaraokeButtonOn();
  684. this.showControlPanel();
  685. }
  686.  
  687. return this;
  688. },
  689. showControlPanel: function()
  690. {
  691. console.log('showpanel');
  692. this.controlPanel = KaraokeUI.controlPanelUI(channelAdjustedValue,
  693. highPassAdjustedValue, lowPassAdjustedValue, gainAdjustedValue);
  694. _loadSetting();
  695. return this;
  696. },
  697. removeControlPanel: function()
  698. {
  699. console.log('hidepanel');
  700. this.controlPanel.remove();
  701.  
  702. return this;
  703. },
  704. isFilterOn: function() {
  705. return karaokeFilterOn;
  706. },
  707. micGainAdjust: function(element)
  708. {
  709. gainAdjustedValue = $(element).val();
  710. _micGain(gainAdjustedValue);
  711.  
  712. return this;
  713. },
  714. channelAdjust: function(element)
  715. {
  716. channelAdjustedValue = parseInt($(element).val());
  717. _adjustChannel();
  718.  
  719. return this;
  720. },
  721. highPassAdjust: function(element)
  722. {
  723. highPassAdjustedValue = parseInt($(element).val());
  724. KaraokeUI.getHighPassAdjustDisplay().html(highPassAdjustedValue.toString());
  725. _adjustChannel()
  726. return this;
  727. },
  728. lowPassAdjust: function(element)
  729. {
  730. lowPassAdjustedValue = parseInt($(element).val());
  731. KaraokeUI.getLowPassAdjustDisplay().html(lowPassAdjustedValue.toString());
  732. _adjustChannel()
  733. return this;
  734. },
  735. loadSetting: function() {
  736. _loadSetting();
  737. }
  738. };
  739. }(jQuery, KaraokeUI);
  740.  
  741. if (typeof audioContext === 'undefined') {
  742. console.log(mediaElement);
  743. console.log(targetContainer);
  744. console.log(UiAttachTo);
  745. console.log("Loading setting");
  746. KaraokePlugin.loadSetting();
  747. console.log("setting up mic");
  748. KaraokePlugin.setupMic();
  749. console.log("setting up audio source");
  750. KaraokePlugin.setupAudioSource(mediaElement);
  751. console.log("setting up menu");
  752. KaraokePlugin.setupMenu(targetContainer);
  753.  
  754. unsafeWindow.KaraokePluginSwitch = function() {
  755. KaraokePlugin.switch();
  756. }
  757. unsafeWindow.KaraokePluginMicGainAdjust = function(element) {
  758. KaraokePlugin.micGainAdjust(element);
  759. }
  760. unsafeWindow.KaraokePluginChannelAdjust = function(element) {
  761. KaraokePlugin.channelAdjust(element);
  762. }
  763. unsafeWindow.KaraokePluginHighPassAdjust = function(element) {
  764. KaraokePlugin.highPassAdjust(element);
  765. }
  766. unsafeWindow.KaraokePluginLowPassAdjust = function(element) {
  767. KaraokePlugin.lowPassAdjust(element);
  768. }
  769. }
  770.  
  771. window.addEventListener("popstate", (event) => {
  772. console.log('Event: popstate, reload setting');
  773. KaraokePlugin.loadSetting();
  774. if(KaraokePlugin.isFilterOn()) {
  775. KaraokePlugin.switch();
  776. KaraokePlugin.switch();
  777. }
  778. });
  779.  
  780. if (window.onurlchange === null) {
  781. console.log('Url Change Event. Setup');
  782. window.addEventListener('urlchange', (info) => {
  783. console.log('Url Changed, reload setting.');
  784. if (window.location.href.includes(urlChangePattern)) {
  785. KaraokePlugin.loadSetting();
  786. if(KaraokePlugin.isFilterOn()) {
  787. KaraokePlugin.switch();
  788. KaraokePlugin.switch();
  789. }
  790. }
  791. });
  792. }
  793.  
  794.  
  795. })(jQuery, md5);