mirror of
https://github.com/pixeltris/TwitchAdSolutions.git
synced 2025-04-29 22:24:29 +02:00
Fix buffering issue after ad segments
- Also switch to an MutationObserver model for mute-black / vide-swap #16
This commit is contained in:
parent
a6928c9d04
commit
2424ab3cc1
@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name TwitchAdSolutions
|
// @name TwitchAdSolutions
|
||||||
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
||||||
// @version 1.3
|
// @version 1.4
|
||||||
// @description Multiple solutions for blocking Twitch ads
|
// @description Multiple solutions for blocking Twitch ads
|
||||||
// @author pixeltris
|
// @author pixeltris
|
||||||
// @match *://*.twitch.tv/*
|
// @match *://*.twitch.tv/*
|
||||||
@ -58,8 +58,6 @@
|
|||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.CurrentChannelNameFromM3U8 = null;
|
scope.CurrentChannelNameFromM3U8 = null;
|
||||||
scope.LastAdUrl = null;
|
|
||||||
scope.LastAdTime = 0;
|
|
||||||
// Need this in both scopes. Window scope needs to update this to worker scope.
|
// Need this in both scopes. Window scope needs to update this to worker scope.
|
||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
}
|
}
|
||||||
@ -91,6 +89,7 @@
|
|||||||
${processM3U8.toString()}
|
${processM3U8.toString()}
|
||||||
${getSegmentInfos.toString()}
|
${getSegmentInfos.toString()}
|
||||||
${getSegmentInfosLines.toString()}
|
${getSegmentInfosLines.toString()}
|
||||||
|
${getFinalSegUrl.toString()}
|
||||||
${hookWorkerFetch.toString()}
|
${hookWorkerFetch.toString()}
|
||||||
${declareOptions.toString()}
|
${declareOptions.toString()}
|
||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
@ -130,6 +129,9 @@
|
|||||||
else if (e.data.key == 'UboReloadPlayer') {
|
else if (e.data.key == 'UboReloadPlayer') {
|
||||||
reloadTwitchPlayer();
|
reloadTwitchPlayer();
|
||||||
}
|
}
|
||||||
|
else if (e.data.key == 'UboPauseResumePlayer') {
|
||||||
|
reloadTwitchPlayer(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getAdDiv() {
|
function getAdDiv() {
|
||||||
var playerRootDiv = document.querySelector('.video-player');
|
var playerRootDiv = document.querySelector('.video-player');
|
||||||
@ -205,7 +207,7 @@
|
|||||||
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
||||||
segInfo.dateTimeLineIndex = i - 2;
|
segInfo.dateTimeLineIndex = i - 2;
|
||||||
segInfo.dateTimeLine = lines[i - 2];
|
segInfo.dateTimeLine = lines[i - 2];
|
||||||
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
|
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
|
||||||
}
|
}
|
||||||
result.segs.push(segInfo);
|
result.segs.push(segInfo);
|
||||||
}
|
}
|
||||||
@ -261,20 +263,42 @@
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getFinalSegUrl(lines) {
|
||||||
|
for (var i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].startsWith("http")) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
||||||
var si = StreamInfosByUrl[url];
|
var si = StreamInfosByUrl[url];
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
si.BackupSeqNumber = -1;
|
|
||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
||||||
var oldRealSeq = si.RealSeqNumber;
|
var oldRealSeq = si.RealSeqNumber;
|
||||||
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
||||||
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
||||||
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
|
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
|
||||||
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
|
||||||
|
si.FakeSeqNumber = 0;
|
||||||
|
si.BackupSeqNumber = -1;
|
||||||
|
postMessage({key:'UboPauseResumePlayer'});*/
|
||||||
|
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
|
||||||
|
var finalSegUrl = getFinalSegUrl(lines);
|
||||||
|
if (finalSegUrl != si.FinalSegUrl) {
|
||||||
|
si.FinalSegUrl = finalSegUrl;
|
||||||
|
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
|
||||||
|
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
if (jump <= 3) {
|
||||||
|
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
} else if (jump > 0) {
|
||||||
|
si.FakeSeqNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
||||||
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
||||||
}
|
}
|
||||||
@ -323,8 +347,6 @@
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (haveAdTags) {
|
if (haveAdTags) {
|
||||||
LastAdUrl = url;
|
|
||||||
LastAdTime = Date.now();
|
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
if (streamInfo == null) {
|
if (streamInfo == null) {
|
||||||
console.log('Unknown stream url ' + url);
|
console.log('Unknown stream url ' + url);
|
||||||
@ -373,33 +395,12 @@
|
|||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
var newLines = [];
|
var newLines = [];
|
||||||
if (backupM3u8 != null) {
|
if (backupM3u8 != null) {
|
||||||
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
|
|
||||||
if (seqMatch != null) {
|
|
||||||
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
|
|
||||||
if (streamInfo.RealSeqNumber > 0) {
|
|
||||||
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
|
|
||||||
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
|
|
||||||
if (streamInfo.FakeSeqNumber == 0) {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
|
|
||||||
}
|
|
||||||
if (oldBackupSeqNumber == -1) {
|
|
||||||
// First backup sequence, assume +1
|
|
||||||
streamInfo.FakeSeqNumber++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
||||||
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
||||||
newLines.push('#EXTM3U');
|
newLines.push('#EXTM3U');
|
||||||
newLines.push('#EXT-X-VERSION:3');
|
newLines.push('#EXT-X-VERSION:3');
|
||||||
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
||||||
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
||||||
// The following will could cause issues when we stop stripping segments
|
// The following will could cause issues when we stop stripping segments
|
||||||
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
||||||
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
||||||
@ -427,6 +428,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finalSegUrl = getFinalSegUrl(newLines);
|
||||||
|
if (finalSegUrl != streamInfo.FinalSegUrl) {
|
||||||
|
streamInfo.FinalSegUrl = finalSegUrl;
|
||||||
|
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
|
||||||
|
}
|
||||||
|
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
|
||||||
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
||||||
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
||||||
} else {
|
} else {
|
||||||
@ -538,6 +545,7 @@
|
|||||||
streamInfo.RealSeqNumber = -1;
|
streamInfo.RealSeqNumber = -1;
|
||||||
streamInfo.BackupSeqNumber = -1;
|
streamInfo.BackupSeqNumber = -1;
|
||||||
streamInfo.FakeSeqNumber = 0;
|
streamInfo.FakeSeqNumber = 0;
|
||||||
|
streamInfo.FinalSegUrl = null;
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
@ -869,6 +877,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pollForAds() {
|
function pollForAds() {
|
||||||
|
//console.log('pollForAds ' + new Date(Date.now()));
|
||||||
//check ad by looking for text banner
|
//check ad by looking for text banner
|
||||||
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
||||||
var foundAd = false;
|
var foundAd = false;
|
||||||
@ -902,9 +911,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(pollForAds,100);
|
//setTimeout(pollForAds,100);
|
||||||
}
|
}
|
||||||
function reloadTwitchPlayer() {
|
function pollForAdsObserver() {
|
||||||
|
pollForAds();
|
||||||
|
var vids = document.getElementsByClassName('video-player');
|
||||||
|
for (var i = 0; i < vids.length; i++) {
|
||||||
|
var observer = new MutationObserver(pollForAds);
|
||||||
|
observer.observe(vids[i], {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reloadTwitchPlayer(isPausePlay) {
|
||||||
// Taken from ttv-tools / ffz
|
// Taken from ttv-tools / ffz
|
||||||
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
||||||
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
||||||
@ -945,6 +967,11 @@
|
|||||||
if (player.paused) {
|
if (player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isPausePlay) {
|
||||||
|
player.pause();
|
||||||
|
player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
||||||
if (sink && sink.video && sink.video._ffz_compressor) {
|
if (sink && sink.video && sink.video._ffz_compressor) {
|
||||||
const video = sink.video;
|
const video = sink.video;
|
||||||
@ -972,11 +999,11 @@
|
|||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
} else {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hookFetch();
|
hookFetch();
|
||||||
|
@ -49,8 +49,6 @@ twitch-videoad.js application/javascript
|
|||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.CurrentChannelNameFromM3U8 = null;
|
scope.CurrentChannelNameFromM3U8 = null;
|
||||||
scope.LastAdUrl = null;
|
|
||||||
scope.LastAdTime = 0;
|
|
||||||
// Need this in both scopes. Window scope needs to update this to worker scope.
|
// Need this in both scopes. Window scope needs to update this to worker scope.
|
||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
}
|
}
|
||||||
@ -82,6 +80,7 @@ twitch-videoad.js application/javascript
|
|||||||
${processM3U8.toString()}
|
${processM3U8.toString()}
|
||||||
${getSegmentInfos.toString()}
|
${getSegmentInfos.toString()}
|
||||||
${getSegmentInfosLines.toString()}
|
${getSegmentInfosLines.toString()}
|
||||||
|
${getFinalSegUrl.toString()}
|
||||||
${hookWorkerFetch.toString()}
|
${hookWorkerFetch.toString()}
|
||||||
${declareOptions.toString()}
|
${declareOptions.toString()}
|
||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
@ -121,6 +120,9 @@ twitch-videoad.js application/javascript
|
|||||||
else if (e.data.key == 'UboReloadPlayer') {
|
else if (e.data.key == 'UboReloadPlayer') {
|
||||||
reloadTwitchPlayer();
|
reloadTwitchPlayer();
|
||||||
}
|
}
|
||||||
|
else if (e.data.key == 'UboPauseResumePlayer') {
|
||||||
|
reloadTwitchPlayer(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getAdDiv() {
|
function getAdDiv() {
|
||||||
var playerRootDiv = document.querySelector('.video-player');
|
var playerRootDiv = document.querySelector('.video-player');
|
||||||
@ -196,7 +198,7 @@ twitch-videoad.js application/javascript
|
|||||||
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
||||||
segInfo.dateTimeLineIndex = i - 2;
|
segInfo.dateTimeLineIndex = i - 2;
|
||||||
segInfo.dateTimeLine = lines[i - 2];
|
segInfo.dateTimeLine = lines[i - 2];
|
||||||
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
|
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
|
||||||
}
|
}
|
||||||
result.segs.push(segInfo);
|
result.segs.push(segInfo);
|
||||||
}
|
}
|
||||||
@ -252,20 +254,42 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getFinalSegUrl(lines) {
|
||||||
|
for (var i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].startsWith("http")) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
||||||
var si = StreamInfosByUrl[url];
|
var si = StreamInfosByUrl[url];
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
si.BackupSeqNumber = -1;
|
|
||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
||||||
var oldRealSeq = si.RealSeqNumber;
|
var oldRealSeq = si.RealSeqNumber;
|
||||||
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
||||||
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
||||||
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
|
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
|
||||||
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
|
||||||
|
si.FakeSeqNumber = 0;
|
||||||
|
si.BackupSeqNumber = -1;
|
||||||
|
postMessage({key:'UboPauseResumePlayer'});*/
|
||||||
|
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
|
||||||
|
var finalSegUrl = getFinalSegUrl(lines);
|
||||||
|
if (finalSegUrl != si.FinalSegUrl) {
|
||||||
|
si.FinalSegUrl = finalSegUrl;
|
||||||
|
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
|
||||||
|
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
if (jump <= 3) {
|
||||||
|
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
} else if (jump > 0) {
|
||||||
|
si.FakeSeqNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
||||||
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
||||||
}
|
}
|
||||||
@ -314,8 +338,6 @@ twitch-videoad.js application/javascript
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (haveAdTags) {
|
if (haveAdTags) {
|
||||||
LastAdUrl = url;
|
|
||||||
LastAdTime = Date.now();
|
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
if (streamInfo == null) {
|
if (streamInfo == null) {
|
||||||
console.log('Unknown stream url ' + url);
|
console.log('Unknown stream url ' + url);
|
||||||
@ -364,33 +386,12 @@ twitch-videoad.js application/javascript
|
|||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
var newLines = [];
|
var newLines = [];
|
||||||
if (backupM3u8 != null) {
|
if (backupM3u8 != null) {
|
||||||
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
|
|
||||||
if (seqMatch != null) {
|
|
||||||
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
|
|
||||||
if (streamInfo.RealSeqNumber > 0) {
|
|
||||||
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
|
|
||||||
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
|
|
||||||
if (streamInfo.FakeSeqNumber == 0) {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
|
|
||||||
}
|
|
||||||
if (oldBackupSeqNumber == -1) {
|
|
||||||
// First backup sequence, assume +1
|
|
||||||
streamInfo.FakeSeqNumber++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
||||||
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
||||||
newLines.push('#EXTM3U');
|
newLines.push('#EXTM3U');
|
||||||
newLines.push('#EXT-X-VERSION:3');
|
newLines.push('#EXT-X-VERSION:3');
|
||||||
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
||||||
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
||||||
// The following will could cause issues when we stop stripping segments
|
// The following will could cause issues when we stop stripping segments
|
||||||
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
||||||
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
||||||
@ -418,6 +419,12 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finalSegUrl = getFinalSegUrl(newLines);
|
||||||
|
if (finalSegUrl != streamInfo.FinalSegUrl) {
|
||||||
|
streamInfo.FinalSegUrl = finalSegUrl;
|
||||||
|
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
|
||||||
|
}
|
||||||
|
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
|
||||||
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
||||||
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
||||||
} else {
|
} else {
|
||||||
@ -529,6 +536,7 @@ twitch-videoad.js application/javascript
|
|||||||
streamInfo.RealSeqNumber = -1;
|
streamInfo.RealSeqNumber = -1;
|
||||||
streamInfo.BackupSeqNumber = -1;
|
streamInfo.BackupSeqNumber = -1;
|
||||||
streamInfo.FakeSeqNumber = 0;
|
streamInfo.FakeSeqNumber = 0;
|
||||||
|
streamInfo.FinalSegUrl = null;
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
@ -860,6 +868,7 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pollForAds() {
|
function pollForAds() {
|
||||||
|
//console.log('pollForAds ' + new Date(Date.now()));
|
||||||
//check ad by looking for text banner
|
//check ad by looking for text banner
|
||||||
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
||||||
var foundAd = false;
|
var foundAd = false;
|
||||||
@ -893,9 +902,22 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(pollForAds,100);
|
//setTimeout(pollForAds,100);
|
||||||
}
|
}
|
||||||
function reloadTwitchPlayer() {
|
function pollForAdsObserver() {
|
||||||
|
pollForAds();
|
||||||
|
var vids = document.getElementsByClassName('video-player');
|
||||||
|
for (var i = 0; i < vids.length; i++) {
|
||||||
|
var observer = new MutationObserver(pollForAds);
|
||||||
|
observer.observe(vids[i], {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reloadTwitchPlayer(isPausePlay) {
|
||||||
// Taken from ttv-tools / ffz
|
// Taken from ttv-tools / ffz
|
||||||
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
||||||
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
||||||
@ -936,6 +958,11 @@ twitch-videoad.js application/javascript
|
|||||||
if (player.paused) {
|
if (player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isPausePlay) {
|
||||||
|
player.pause();
|
||||||
|
player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
||||||
if (sink && sink.video && sink.video._ffz_compressor) {
|
if (sink && sink.video && sink.video._ffz_compressor) {
|
||||||
const video = sink.video;
|
const video = sink.video;
|
||||||
@ -963,11 +990,11 @@ twitch-videoad.js application/javascript
|
|||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
} else {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hookFetch();
|
hookFetch();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name TwitchAdSolutions
|
// @name TwitchAdSolutions (mute-black)
|
||||||
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
||||||
// @version 1.3
|
// @version 1.4
|
||||||
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/mute-black/mute-black.user.js
|
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/mute-black/mute-black.user.js
|
||||||
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/mute-black/mute-black.user.js
|
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/mute-black/mute-black.user.js
|
||||||
// @description Multiple solutions for blocking Twitch ads (mute-black)
|
// @description Multiple solutions for blocking Twitch ads (mute-black)
|
||||||
@ -60,8 +60,6 @@
|
|||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.CurrentChannelNameFromM3U8 = null;
|
scope.CurrentChannelNameFromM3U8 = null;
|
||||||
scope.LastAdUrl = null;
|
|
||||||
scope.LastAdTime = 0;
|
|
||||||
// Need this in both scopes. Window scope needs to update this to worker scope.
|
// Need this in both scopes. Window scope needs to update this to worker scope.
|
||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
}
|
}
|
||||||
@ -93,6 +91,7 @@
|
|||||||
${processM3U8.toString()}
|
${processM3U8.toString()}
|
||||||
${getSegmentInfos.toString()}
|
${getSegmentInfos.toString()}
|
||||||
${getSegmentInfosLines.toString()}
|
${getSegmentInfosLines.toString()}
|
||||||
|
${getFinalSegUrl.toString()}
|
||||||
${hookWorkerFetch.toString()}
|
${hookWorkerFetch.toString()}
|
||||||
${declareOptions.toString()}
|
${declareOptions.toString()}
|
||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
@ -132,6 +131,9 @@
|
|||||||
else if (e.data.key == 'UboReloadPlayer') {
|
else if (e.data.key == 'UboReloadPlayer') {
|
||||||
reloadTwitchPlayer();
|
reloadTwitchPlayer();
|
||||||
}
|
}
|
||||||
|
else if (e.data.key == 'UboPauseResumePlayer') {
|
||||||
|
reloadTwitchPlayer(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getAdDiv() {
|
function getAdDiv() {
|
||||||
var playerRootDiv = document.querySelector('.video-player');
|
var playerRootDiv = document.querySelector('.video-player');
|
||||||
@ -207,7 +209,7 @@
|
|||||||
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
||||||
segInfo.dateTimeLineIndex = i - 2;
|
segInfo.dateTimeLineIndex = i - 2;
|
||||||
segInfo.dateTimeLine = lines[i - 2];
|
segInfo.dateTimeLine = lines[i - 2];
|
||||||
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
|
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
|
||||||
}
|
}
|
||||||
result.segs.push(segInfo);
|
result.segs.push(segInfo);
|
||||||
}
|
}
|
||||||
@ -263,20 +265,42 @@
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getFinalSegUrl(lines) {
|
||||||
|
for (var i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].startsWith("http")) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
||||||
var si = StreamInfosByUrl[url];
|
var si = StreamInfosByUrl[url];
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
si.BackupSeqNumber = -1;
|
|
||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
||||||
var oldRealSeq = si.RealSeqNumber;
|
var oldRealSeq = si.RealSeqNumber;
|
||||||
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
||||||
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
||||||
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
|
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
|
||||||
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
|
||||||
|
si.FakeSeqNumber = 0;
|
||||||
|
si.BackupSeqNumber = -1;
|
||||||
|
postMessage({key:'UboPauseResumePlayer'});*/
|
||||||
|
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
|
||||||
|
var finalSegUrl = getFinalSegUrl(lines);
|
||||||
|
if (finalSegUrl != si.FinalSegUrl) {
|
||||||
|
si.FinalSegUrl = finalSegUrl;
|
||||||
|
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
|
||||||
|
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
if (jump <= 3) {
|
||||||
|
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
} else if (jump > 0) {
|
||||||
|
si.FakeSeqNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
||||||
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
||||||
}
|
}
|
||||||
@ -325,8 +349,6 @@
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (haveAdTags) {
|
if (haveAdTags) {
|
||||||
LastAdUrl = url;
|
|
||||||
LastAdTime = Date.now();
|
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
if (streamInfo == null) {
|
if (streamInfo == null) {
|
||||||
console.log('Unknown stream url ' + url);
|
console.log('Unknown stream url ' + url);
|
||||||
@ -375,33 +397,12 @@
|
|||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
var newLines = [];
|
var newLines = [];
|
||||||
if (backupM3u8 != null) {
|
if (backupM3u8 != null) {
|
||||||
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
|
|
||||||
if (seqMatch != null) {
|
|
||||||
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
|
|
||||||
if (streamInfo.RealSeqNumber > 0) {
|
|
||||||
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
|
|
||||||
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
|
|
||||||
if (streamInfo.FakeSeqNumber == 0) {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
|
|
||||||
}
|
|
||||||
if (oldBackupSeqNumber == -1) {
|
|
||||||
// First backup sequence, assume +1
|
|
||||||
streamInfo.FakeSeqNumber++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
||||||
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
||||||
newLines.push('#EXTM3U');
|
newLines.push('#EXTM3U');
|
||||||
newLines.push('#EXT-X-VERSION:3');
|
newLines.push('#EXT-X-VERSION:3');
|
||||||
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
||||||
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
||||||
// The following will could cause issues when we stop stripping segments
|
// The following will could cause issues when we stop stripping segments
|
||||||
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
||||||
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
||||||
@ -429,6 +430,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finalSegUrl = getFinalSegUrl(newLines);
|
||||||
|
if (finalSegUrl != streamInfo.FinalSegUrl) {
|
||||||
|
streamInfo.FinalSegUrl = finalSegUrl;
|
||||||
|
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
|
||||||
|
}
|
||||||
|
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
|
||||||
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
||||||
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
||||||
} else {
|
} else {
|
||||||
@ -540,6 +547,7 @@
|
|||||||
streamInfo.RealSeqNumber = -1;
|
streamInfo.RealSeqNumber = -1;
|
||||||
streamInfo.BackupSeqNumber = -1;
|
streamInfo.BackupSeqNumber = -1;
|
||||||
streamInfo.FakeSeqNumber = 0;
|
streamInfo.FakeSeqNumber = 0;
|
||||||
|
streamInfo.FinalSegUrl = null;
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
@ -871,6 +879,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pollForAds() {
|
function pollForAds() {
|
||||||
|
//console.log('pollForAds ' + new Date(Date.now()));
|
||||||
//check ad by looking for text banner
|
//check ad by looking for text banner
|
||||||
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
||||||
var foundAd = false;
|
var foundAd = false;
|
||||||
@ -904,9 +913,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(pollForAds,100);
|
//setTimeout(pollForAds,100);
|
||||||
}
|
}
|
||||||
function reloadTwitchPlayer() {
|
function pollForAdsObserver() {
|
||||||
|
pollForAds();
|
||||||
|
var vids = document.getElementsByClassName('video-player');
|
||||||
|
for (var i = 0; i < vids.length; i++) {
|
||||||
|
var observer = new MutationObserver(pollForAds);
|
||||||
|
observer.observe(vids[i], {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reloadTwitchPlayer(isPausePlay) {
|
||||||
// Taken from ttv-tools / ffz
|
// Taken from ttv-tools / ffz
|
||||||
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
||||||
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
||||||
@ -947,6 +969,11 @@
|
|||||||
if (player.paused) {
|
if (player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isPausePlay) {
|
||||||
|
player.pause();
|
||||||
|
player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
||||||
if (sink && sink.video && sink.video._ffz_compressor) {
|
if (sink && sink.video && sink.video._ffz_compressor) {
|
||||||
const video = sink.video;
|
const video = sink.video;
|
||||||
@ -974,11 +1001,11 @@
|
|||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
} else {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hookFetch();
|
hookFetch();
|
||||||
|
@ -49,8 +49,6 @@ twitch-videoad.js application/javascript
|
|||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.CurrentChannelNameFromM3U8 = null;
|
scope.CurrentChannelNameFromM3U8 = null;
|
||||||
scope.LastAdUrl = null;
|
|
||||||
scope.LastAdTime = 0;
|
|
||||||
// Need this in both scopes. Window scope needs to update this to worker scope.
|
// Need this in both scopes. Window scope needs to update this to worker scope.
|
||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
}
|
}
|
||||||
@ -82,6 +80,7 @@ twitch-videoad.js application/javascript
|
|||||||
${processM3U8.toString()}
|
${processM3U8.toString()}
|
||||||
${getSegmentInfos.toString()}
|
${getSegmentInfos.toString()}
|
||||||
${getSegmentInfosLines.toString()}
|
${getSegmentInfosLines.toString()}
|
||||||
|
${getFinalSegUrl.toString()}
|
||||||
${hookWorkerFetch.toString()}
|
${hookWorkerFetch.toString()}
|
||||||
${declareOptions.toString()}
|
${declareOptions.toString()}
|
||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
@ -121,6 +120,9 @@ twitch-videoad.js application/javascript
|
|||||||
else if (e.data.key == 'UboReloadPlayer') {
|
else if (e.data.key == 'UboReloadPlayer') {
|
||||||
reloadTwitchPlayer();
|
reloadTwitchPlayer();
|
||||||
}
|
}
|
||||||
|
else if (e.data.key == 'UboPauseResumePlayer') {
|
||||||
|
reloadTwitchPlayer(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getAdDiv() {
|
function getAdDiv() {
|
||||||
var playerRootDiv = document.querySelector('.video-player');
|
var playerRootDiv = document.querySelector('.video-player');
|
||||||
@ -196,7 +198,7 @@ twitch-videoad.js application/javascript
|
|||||||
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
||||||
segInfo.dateTimeLineIndex = i - 2;
|
segInfo.dateTimeLineIndex = i - 2;
|
||||||
segInfo.dateTimeLine = lines[i - 2];
|
segInfo.dateTimeLine = lines[i - 2];
|
||||||
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
|
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
|
||||||
}
|
}
|
||||||
result.segs.push(segInfo);
|
result.segs.push(segInfo);
|
||||||
}
|
}
|
||||||
@ -252,20 +254,42 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getFinalSegUrl(lines) {
|
||||||
|
for (var i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].startsWith("http")) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
||||||
var si = StreamInfosByUrl[url];
|
var si = StreamInfosByUrl[url];
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
si.BackupSeqNumber = -1;
|
|
||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
||||||
var oldRealSeq = si.RealSeqNumber;
|
var oldRealSeq = si.RealSeqNumber;
|
||||||
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
||||||
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
||||||
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
|
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
|
||||||
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
|
||||||
|
si.FakeSeqNumber = 0;
|
||||||
|
si.BackupSeqNumber = -1;
|
||||||
|
postMessage({key:'UboPauseResumePlayer'});*/
|
||||||
|
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
|
||||||
|
var finalSegUrl = getFinalSegUrl(lines);
|
||||||
|
if (finalSegUrl != si.FinalSegUrl) {
|
||||||
|
si.FinalSegUrl = finalSegUrl;
|
||||||
|
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
|
||||||
|
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
if (jump <= 3) {
|
||||||
|
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
} else if (jump > 0) {
|
||||||
|
si.FakeSeqNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
||||||
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
||||||
}
|
}
|
||||||
@ -314,8 +338,6 @@ twitch-videoad.js application/javascript
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (haveAdTags) {
|
if (haveAdTags) {
|
||||||
LastAdUrl = url;
|
|
||||||
LastAdTime = Date.now();
|
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
if (streamInfo == null) {
|
if (streamInfo == null) {
|
||||||
console.log('Unknown stream url ' + url);
|
console.log('Unknown stream url ' + url);
|
||||||
@ -364,33 +386,12 @@ twitch-videoad.js application/javascript
|
|||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
var newLines = [];
|
var newLines = [];
|
||||||
if (backupM3u8 != null) {
|
if (backupM3u8 != null) {
|
||||||
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
|
|
||||||
if (seqMatch != null) {
|
|
||||||
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
|
|
||||||
if (streamInfo.RealSeqNumber > 0) {
|
|
||||||
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
|
|
||||||
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
|
|
||||||
if (streamInfo.FakeSeqNumber == 0) {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
|
|
||||||
}
|
|
||||||
if (oldBackupSeqNumber == -1) {
|
|
||||||
// First backup sequence, assume +1
|
|
||||||
streamInfo.FakeSeqNumber++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
||||||
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
||||||
newLines.push('#EXTM3U');
|
newLines.push('#EXTM3U');
|
||||||
newLines.push('#EXT-X-VERSION:3');
|
newLines.push('#EXT-X-VERSION:3');
|
||||||
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
||||||
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
||||||
// The following will could cause issues when we stop stripping segments
|
// The following will could cause issues when we stop stripping segments
|
||||||
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
||||||
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
||||||
@ -418,6 +419,12 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finalSegUrl = getFinalSegUrl(newLines);
|
||||||
|
if (finalSegUrl != streamInfo.FinalSegUrl) {
|
||||||
|
streamInfo.FinalSegUrl = finalSegUrl;
|
||||||
|
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
|
||||||
|
}
|
||||||
|
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
|
||||||
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
||||||
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
||||||
} else {
|
} else {
|
||||||
@ -529,6 +536,7 @@ twitch-videoad.js application/javascript
|
|||||||
streamInfo.RealSeqNumber = -1;
|
streamInfo.RealSeqNumber = -1;
|
||||||
streamInfo.BackupSeqNumber = -1;
|
streamInfo.BackupSeqNumber = -1;
|
||||||
streamInfo.FakeSeqNumber = 0;
|
streamInfo.FakeSeqNumber = 0;
|
||||||
|
streamInfo.FinalSegUrl = null;
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
@ -860,6 +868,7 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pollForAds() {
|
function pollForAds() {
|
||||||
|
//console.log('pollForAds ' + new Date(Date.now()));
|
||||||
//check ad by looking for text banner
|
//check ad by looking for text banner
|
||||||
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
||||||
var foundAd = false;
|
var foundAd = false;
|
||||||
@ -893,9 +902,22 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(pollForAds,100);
|
//setTimeout(pollForAds,100);
|
||||||
}
|
}
|
||||||
function reloadTwitchPlayer() {
|
function pollForAdsObserver() {
|
||||||
|
pollForAds();
|
||||||
|
var vids = document.getElementsByClassName('video-player');
|
||||||
|
for (var i = 0; i < vids.length; i++) {
|
||||||
|
var observer = new MutationObserver(pollForAds);
|
||||||
|
observer.observe(vids[i], {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reloadTwitchPlayer(isPausePlay) {
|
||||||
// Taken from ttv-tools / ffz
|
// Taken from ttv-tools / ffz
|
||||||
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
||||||
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
||||||
@ -936,6 +958,11 @@ twitch-videoad.js application/javascript
|
|||||||
if (player.paused) {
|
if (player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isPausePlay) {
|
||||||
|
player.pause();
|
||||||
|
player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
||||||
if (sink && sink.video && sink.video._ffz_compressor) {
|
if (sink && sink.video && sink.video._ffz_compressor) {
|
||||||
const video = sink.video;
|
const video = sink.video;
|
||||||
@ -963,11 +990,11 @@ twitch-videoad.js application/javascript
|
|||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
} else {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hookFetch();
|
hookFetch();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name TwitchAdSolutions
|
// @name TwitchAdSolutions (notify-reload)
|
||||||
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
||||||
// @version 1.3
|
// @version 1.4
|
||||||
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-reload/notify-reload.user.js
|
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-reload/notify-reload.user.js
|
||||||
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-reload/notify-reload.user.js
|
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-reload/notify-reload.user.js
|
||||||
// @description Multiple solutions for blocking Twitch ads (notify-reload)
|
// @description Multiple solutions for blocking Twitch ads (notify-reload)
|
||||||
@ -60,8 +60,6 @@
|
|||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.CurrentChannelNameFromM3U8 = null;
|
scope.CurrentChannelNameFromM3U8 = null;
|
||||||
scope.LastAdUrl = null;
|
|
||||||
scope.LastAdTime = 0;
|
|
||||||
// Need this in both scopes. Window scope needs to update this to worker scope.
|
// Need this in both scopes. Window scope needs to update this to worker scope.
|
||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
}
|
}
|
||||||
@ -93,6 +91,7 @@
|
|||||||
${processM3U8.toString()}
|
${processM3U8.toString()}
|
||||||
${getSegmentInfos.toString()}
|
${getSegmentInfos.toString()}
|
||||||
${getSegmentInfosLines.toString()}
|
${getSegmentInfosLines.toString()}
|
||||||
|
${getFinalSegUrl.toString()}
|
||||||
${hookWorkerFetch.toString()}
|
${hookWorkerFetch.toString()}
|
||||||
${declareOptions.toString()}
|
${declareOptions.toString()}
|
||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
@ -132,6 +131,9 @@
|
|||||||
else if (e.data.key == 'UboReloadPlayer') {
|
else if (e.data.key == 'UboReloadPlayer') {
|
||||||
reloadTwitchPlayer();
|
reloadTwitchPlayer();
|
||||||
}
|
}
|
||||||
|
else if (e.data.key == 'UboPauseResumePlayer') {
|
||||||
|
reloadTwitchPlayer(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getAdDiv() {
|
function getAdDiv() {
|
||||||
var playerRootDiv = document.querySelector('.video-player');
|
var playerRootDiv = document.querySelector('.video-player');
|
||||||
@ -207,7 +209,7 @@
|
|||||||
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
||||||
segInfo.dateTimeLineIndex = i - 2;
|
segInfo.dateTimeLineIndex = i - 2;
|
||||||
segInfo.dateTimeLine = lines[i - 2];
|
segInfo.dateTimeLine = lines[i - 2];
|
||||||
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
|
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
|
||||||
}
|
}
|
||||||
result.segs.push(segInfo);
|
result.segs.push(segInfo);
|
||||||
}
|
}
|
||||||
@ -263,20 +265,42 @@
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getFinalSegUrl(lines) {
|
||||||
|
for (var i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].startsWith("http")) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
||||||
var si = StreamInfosByUrl[url];
|
var si = StreamInfosByUrl[url];
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
si.BackupSeqNumber = -1;
|
|
||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
||||||
var oldRealSeq = si.RealSeqNumber;
|
var oldRealSeq = si.RealSeqNumber;
|
||||||
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
||||||
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
||||||
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
|
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
|
||||||
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
|
||||||
|
si.FakeSeqNumber = 0;
|
||||||
|
si.BackupSeqNumber = -1;
|
||||||
|
postMessage({key:'UboPauseResumePlayer'});*/
|
||||||
|
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
|
||||||
|
var finalSegUrl = getFinalSegUrl(lines);
|
||||||
|
if (finalSegUrl != si.FinalSegUrl) {
|
||||||
|
si.FinalSegUrl = finalSegUrl;
|
||||||
|
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
|
||||||
|
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
if (jump <= 3) {
|
||||||
|
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
} else if (jump > 0) {
|
||||||
|
si.FakeSeqNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
||||||
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
||||||
}
|
}
|
||||||
@ -325,8 +349,6 @@
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (haveAdTags) {
|
if (haveAdTags) {
|
||||||
LastAdUrl = url;
|
|
||||||
LastAdTime = Date.now();
|
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
if (streamInfo == null) {
|
if (streamInfo == null) {
|
||||||
console.log('Unknown stream url ' + url);
|
console.log('Unknown stream url ' + url);
|
||||||
@ -375,33 +397,12 @@
|
|||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
var newLines = [];
|
var newLines = [];
|
||||||
if (backupM3u8 != null) {
|
if (backupM3u8 != null) {
|
||||||
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
|
|
||||||
if (seqMatch != null) {
|
|
||||||
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
|
|
||||||
if (streamInfo.RealSeqNumber > 0) {
|
|
||||||
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
|
|
||||||
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
|
|
||||||
if (streamInfo.FakeSeqNumber == 0) {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
|
|
||||||
}
|
|
||||||
if (oldBackupSeqNumber == -1) {
|
|
||||||
// First backup sequence, assume +1
|
|
||||||
streamInfo.FakeSeqNumber++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
||||||
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
||||||
newLines.push('#EXTM3U');
|
newLines.push('#EXTM3U');
|
||||||
newLines.push('#EXT-X-VERSION:3');
|
newLines.push('#EXT-X-VERSION:3');
|
||||||
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
||||||
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
||||||
// The following will could cause issues when we stop stripping segments
|
// The following will could cause issues when we stop stripping segments
|
||||||
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
||||||
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
||||||
@ -429,6 +430,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finalSegUrl = getFinalSegUrl(newLines);
|
||||||
|
if (finalSegUrl != streamInfo.FinalSegUrl) {
|
||||||
|
streamInfo.FinalSegUrl = finalSegUrl;
|
||||||
|
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
|
||||||
|
}
|
||||||
|
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
|
||||||
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
||||||
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
||||||
} else {
|
} else {
|
||||||
@ -540,6 +547,7 @@
|
|||||||
streamInfo.RealSeqNumber = -1;
|
streamInfo.RealSeqNumber = -1;
|
||||||
streamInfo.BackupSeqNumber = -1;
|
streamInfo.BackupSeqNumber = -1;
|
||||||
streamInfo.FakeSeqNumber = 0;
|
streamInfo.FakeSeqNumber = 0;
|
||||||
|
streamInfo.FinalSegUrl = null;
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
@ -871,6 +879,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pollForAds() {
|
function pollForAds() {
|
||||||
|
//console.log('pollForAds ' + new Date(Date.now()));
|
||||||
//check ad by looking for text banner
|
//check ad by looking for text banner
|
||||||
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
||||||
var foundAd = false;
|
var foundAd = false;
|
||||||
@ -904,9 +913,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(pollForAds,100);
|
//setTimeout(pollForAds,100);
|
||||||
}
|
}
|
||||||
function reloadTwitchPlayer() {
|
function pollForAdsObserver() {
|
||||||
|
pollForAds();
|
||||||
|
var vids = document.getElementsByClassName('video-player');
|
||||||
|
for (var i = 0; i < vids.length; i++) {
|
||||||
|
var observer = new MutationObserver(pollForAds);
|
||||||
|
observer.observe(vids[i], {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reloadTwitchPlayer(isPausePlay) {
|
||||||
// Taken from ttv-tools / ffz
|
// Taken from ttv-tools / ffz
|
||||||
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
||||||
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
||||||
@ -947,6 +969,11 @@
|
|||||||
if (player.paused) {
|
if (player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isPausePlay) {
|
||||||
|
player.pause();
|
||||||
|
player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
||||||
if (sink && sink.video && sink.video._ffz_compressor) {
|
if (sink && sink.video && sink.video._ffz_compressor) {
|
||||||
const video = sink.video;
|
const video = sink.video;
|
||||||
@ -974,11 +1001,11 @@
|
|||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
} else {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hookFetch();
|
hookFetch();
|
||||||
|
@ -49,8 +49,6 @@ twitch-videoad.js application/javascript
|
|||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.CurrentChannelNameFromM3U8 = null;
|
scope.CurrentChannelNameFromM3U8 = null;
|
||||||
scope.LastAdUrl = null;
|
|
||||||
scope.LastAdTime = 0;
|
|
||||||
// Need this in both scopes. Window scope needs to update this to worker scope.
|
// Need this in both scopes. Window scope needs to update this to worker scope.
|
||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
}
|
}
|
||||||
@ -82,6 +80,7 @@ twitch-videoad.js application/javascript
|
|||||||
${processM3U8.toString()}
|
${processM3U8.toString()}
|
||||||
${getSegmentInfos.toString()}
|
${getSegmentInfos.toString()}
|
||||||
${getSegmentInfosLines.toString()}
|
${getSegmentInfosLines.toString()}
|
||||||
|
${getFinalSegUrl.toString()}
|
||||||
${hookWorkerFetch.toString()}
|
${hookWorkerFetch.toString()}
|
||||||
${declareOptions.toString()}
|
${declareOptions.toString()}
|
||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
@ -121,6 +120,9 @@ twitch-videoad.js application/javascript
|
|||||||
else if (e.data.key == 'UboReloadPlayer') {
|
else if (e.data.key == 'UboReloadPlayer') {
|
||||||
reloadTwitchPlayer();
|
reloadTwitchPlayer();
|
||||||
}
|
}
|
||||||
|
else if (e.data.key == 'UboPauseResumePlayer') {
|
||||||
|
reloadTwitchPlayer(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getAdDiv() {
|
function getAdDiv() {
|
||||||
var playerRootDiv = document.querySelector('.video-player');
|
var playerRootDiv = document.querySelector('.video-player');
|
||||||
@ -196,7 +198,7 @@ twitch-videoad.js application/javascript
|
|||||||
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
||||||
segInfo.dateTimeLineIndex = i - 2;
|
segInfo.dateTimeLineIndex = i - 2;
|
||||||
segInfo.dateTimeLine = lines[i - 2];
|
segInfo.dateTimeLine = lines[i - 2];
|
||||||
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
|
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
|
||||||
}
|
}
|
||||||
result.segs.push(segInfo);
|
result.segs.push(segInfo);
|
||||||
}
|
}
|
||||||
@ -252,20 +254,42 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getFinalSegUrl(lines) {
|
||||||
|
for (var i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].startsWith("http")) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
||||||
var si = StreamInfosByUrl[url];
|
var si = StreamInfosByUrl[url];
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
si.BackupSeqNumber = -1;
|
|
||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
||||||
var oldRealSeq = si.RealSeqNumber;
|
var oldRealSeq = si.RealSeqNumber;
|
||||||
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
||||||
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
||||||
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
|
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
|
||||||
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
|
||||||
|
si.FakeSeqNumber = 0;
|
||||||
|
si.BackupSeqNumber = -1;
|
||||||
|
postMessage({key:'UboPauseResumePlayer'});*/
|
||||||
|
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
|
||||||
|
var finalSegUrl = getFinalSegUrl(lines);
|
||||||
|
if (finalSegUrl != si.FinalSegUrl) {
|
||||||
|
si.FinalSegUrl = finalSegUrl;
|
||||||
|
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
|
||||||
|
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
if (jump <= 3) {
|
||||||
|
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
} else if (jump > 0) {
|
||||||
|
si.FakeSeqNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
||||||
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
||||||
}
|
}
|
||||||
@ -314,8 +338,6 @@ twitch-videoad.js application/javascript
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (haveAdTags) {
|
if (haveAdTags) {
|
||||||
LastAdUrl = url;
|
|
||||||
LastAdTime = Date.now();
|
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
if (streamInfo == null) {
|
if (streamInfo == null) {
|
||||||
console.log('Unknown stream url ' + url);
|
console.log('Unknown stream url ' + url);
|
||||||
@ -364,33 +386,12 @@ twitch-videoad.js application/javascript
|
|||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
var newLines = [];
|
var newLines = [];
|
||||||
if (backupM3u8 != null) {
|
if (backupM3u8 != null) {
|
||||||
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
|
|
||||||
if (seqMatch != null) {
|
|
||||||
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
|
|
||||||
if (streamInfo.RealSeqNumber > 0) {
|
|
||||||
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
|
|
||||||
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
|
|
||||||
if (streamInfo.FakeSeqNumber == 0) {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
|
|
||||||
}
|
|
||||||
if (oldBackupSeqNumber == -1) {
|
|
||||||
// First backup sequence, assume +1
|
|
||||||
streamInfo.FakeSeqNumber++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
||||||
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
||||||
newLines.push('#EXTM3U');
|
newLines.push('#EXTM3U');
|
||||||
newLines.push('#EXT-X-VERSION:3');
|
newLines.push('#EXT-X-VERSION:3');
|
||||||
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
||||||
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
||||||
// The following will could cause issues when we stop stripping segments
|
// The following will could cause issues when we stop stripping segments
|
||||||
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
||||||
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
||||||
@ -418,6 +419,12 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finalSegUrl = getFinalSegUrl(newLines);
|
||||||
|
if (finalSegUrl != streamInfo.FinalSegUrl) {
|
||||||
|
streamInfo.FinalSegUrl = finalSegUrl;
|
||||||
|
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
|
||||||
|
}
|
||||||
|
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
|
||||||
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
||||||
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
||||||
} else {
|
} else {
|
||||||
@ -529,6 +536,7 @@ twitch-videoad.js application/javascript
|
|||||||
streamInfo.RealSeqNumber = -1;
|
streamInfo.RealSeqNumber = -1;
|
||||||
streamInfo.BackupSeqNumber = -1;
|
streamInfo.BackupSeqNumber = -1;
|
||||||
streamInfo.FakeSeqNumber = 0;
|
streamInfo.FakeSeqNumber = 0;
|
||||||
|
streamInfo.FinalSegUrl = null;
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
@ -860,6 +868,7 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pollForAds() {
|
function pollForAds() {
|
||||||
|
//console.log('pollForAds ' + new Date(Date.now()));
|
||||||
//check ad by looking for text banner
|
//check ad by looking for text banner
|
||||||
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
||||||
var foundAd = false;
|
var foundAd = false;
|
||||||
@ -893,9 +902,22 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(pollForAds,100);
|
//setTimeout(pollForAds,100);
|
||||||
}
|
}
|
||||||
function reloadTwitchPlayer() {
|
function pollForAdsObserver() {
|
||||||
|
pollForAds();
|
||||||
|
var vids = document.getElementsByClassName('video-player');
|
||||||
|
for (var i = 0; i < vids.length; i++) {
|
||||||
|
var observer = new MutationObserver(pollForAds);
|
||||||
|
observer.observe(vids[i], {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reloadTwitchPlayer(isPausePlay) {
|
||||||
// Taken from ttv-tools / ffz
|
// Taken from ttv-tools / ffz
|
||||||
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
||||||
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
||||||
@ -936,6 +958,11 @@ twitch-videoad.js application/javascript
|
|||||||
if (player.paused) {
|
if (player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isPausePlay) {
|
||||||
|
player.pause();
|
||||||
|
player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
||||||
if (sink && sink.video && sink.video._ffz_compressor) {
|
if (sink && sink.video && sink.video._ffz_compressor) {
|
||||||
const video = sink.video;
|
const video = sink.video;
|
||||||
@ -963,11 +990,11 @@ twitch-videoad.js application/javascript
|
|||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
} else {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hookFetch();
|
hookFetch();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name TwitchAdSolutions
|
// @name TwitchAdSolutions (notify-strip-reload)
|
||||||
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
||||||
// @version 1.3
|
// @version 1.4
|
||||||
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip-reload/notify-strip-reload.user.js
|
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip-reload/notify-strip-reload.user.js
|
||||||
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip-reload/notify-strip-reload.user.js
|
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip-reload/notify-strip-reload.user.js
|
||||||
// @description Multiple solutions for blocking Twitch ads (notify-strip-reload)
|
// @description Multiple solutions for blocking Twitch ads (notify-strip-reload)
|
||||||
@ -60,8 +60,6 @@
|
|||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.CurrentChannelNameFromM3U8 = null;
|
scope.CurrentChannelNameFromM3U8 = null;
|
||||||
scope.LastAdUrl = null;
|
|
||||||
scope.LastAdTime = 0;
|
|
||||||
// Need this in both scopes. Window scope needs to update this to worker scope.
|
// Need this in both scopes. Window scope needs to update this to worker scope.
|
||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
}
|
}
|
||||||
@ -93,6 +91,7 @@
|
|||||||
${processM3U8.toString()}
|
${processM3U8.toString()}
|
||||||
${getSegmentInfos.toString()}
|
${getSegmentInfos.toString()}
|
||||||
${getSegmentInfosLines.toString()}
|
${getSegmentInfosLines.toString()}
|
||||||
|
${getFinalSegUrl.toString()}
|
||||||
${hookWorkerFetch.toString()}
|
${hookWorkerFetch.toString()}
|
||||||
${declareOptions.toString()}
|
${declareOptions.toString()}
|
||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
@ -132,6 +131,9 @@
|
|||||||
else if (e.data.key == 'UboReloadPlayer') {
|
else if (e.data.key == 'UboReloadPlayer') {
|
||||||
reloadTwitchPlayer();
|
reloadTwitchPlayer();
|
||||||
}
|
}
|
||||||
|
else if (e.data.key == 'UboPauseResumePlayer') {
|
||||||
|
reloadTwitchPlayer(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getAdDiv() {
|
function getAdDiv() {
|
||||||
var playerRootDiv = document.querySelector('.video-player');
|
var playerRootDiv = document.querySelector('.video-player');
|
||||||
@ -207,7 +209,7 @@
|
|||||||
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
||||||
segInfo.dateTimeLineIndex = i - 2;
|
segInfo.dateTimeLineIndex = i - 2;
|
||||||
segInfo.dateTimeLine = lines[i - 2];
|
segInfo.dateTimeLine = lines[i - 2];
|
||||||
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
|
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
|
||||||
}
|
}
|
||||||
result.segs.push(segInfo);
|
result.segs.push(segInfo);
|
||||||
}
|
}
|
||||||
@ -263,20 +265,42 @@
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getFinalSegUrl(lines) {
|
||||||
|
for (var i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].startsWith("http")) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
||||||
var si = StreamInfosByUrl[url];
|
var si = StreamInfosByUrl[url];
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
si.BackupSeqNumber = -1;
|
|
||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
||||||
var oldRealSeq = si.RealSeqNumber;
|
var oldRealSeq = si.RealSeqNumber;
|
||||||
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
||||||
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
||||||
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
|
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
|
||||||
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
|
||||||
|
si.FakeSeqNumber = 0;
|
||||||
|
si.BackupSeqNumber = -1;
|
||||||
|
postMessage({key:'UboPauseResumePlayer'});*/
|
||||||
|
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
|
||||||
|
var finalSegUrl = getFinalSegUrl(lines);
|
||||||
|
if (finalSegUrl != si.FinalSegUrl) {
|
||||||
|
si.FinalSegUrl = finalSegUrl;
|
||||||
|
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
|
||||||
|
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
if (jump <= 3) {
|
||||||
|
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
} else if (jump > 0) {
|
||||||
|
si.FakeSeqNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
||||||
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
||||||
}
|
}
|
||||||
@ -325,8 +349,6 @@
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (haveAdTags) {
|
if (haveAdTags) {
|
||||||
LastAdUrl = url;
|
|
||||||
LastAdTime = Date.now();
|
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
if (streamInfo == null) {
|
if (streamInfo == null) {
|
||||||
console.log('Unknown stream url ' + url);
|
console.log('Unknown stream url ' + url);
|
||||||
@ -375,33 +397,12 @@
|
|||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
var newLines = [];
|
var newLines = [];
|
||||||
if (backupM3u8 != null) {
|
if (backupM3u8 != null) {
|
||||||
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
|
|
||||||
if (seqMatch != null) {
|
|
||||||
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
|
|
||||||
if (streamInfo.RealSeqNumber > 0) {
|
|
||||||
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
|
|
||||||
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
|
|
||||||
if (streamInfo.FakeSeqNumber == 0) {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
|
|
||||||
}
|
|
||||||
if (oldBackupSeqNumber == -1) {
|
|
||||||
// First backup sequence, assume +1
|
|
||||||
streamInfo.FakeSeqNumber++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
||||||
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
||||||
newLines.push('#EXTM3U');
|
newLines.push('#EXTM3U');
|
||||||
newLines.push('#EXT-X-VERSION:3');
|
newLines.push('#EXT-X-VERSION:3');
|
||||||
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
||||||
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
||||||
// The following will could cause issues when we stop stripping segments
|
// The following will could cause issues when we stop stripping segments
|
||||||
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
||||||
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
||||||
@ -429,6 +430,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finalSegUrl = getFinalSegUrl(newLines);
|
||||||
|
if (finalSegUrl != streamInfo.FinalSegUrl) {
|
||||||
|
streamInfo.FinalSegUrl = finalSegUrl;
|
||||||
|
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
|
||||||
|
}
|
||||||
|
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
|
||||||
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
||||||
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
||||||
} else {
|
} else {
|
||||||
@ -540,6 +547,7 @@
|
|||||||
streamInfo.RealSeqNumber = -1;
|
streamInfo.RealSeqNumber = -1;
|
||||||
streamInfo.BackupSeqNumber = -1;
|
streamInfo.BackupSeqNumber = -1;
|
||||||
streamInfo.FakeSeqNumber = 0;
|
streamInfo.FakeSeqNumber = 0;
|
||||||
|
streamInfo.FinalSegUrl = null;
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
@ -871,6 +879,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pollForAds() {
|
function pollForAds() {
|
||||||
|
//console.log('pollForAds ' + new Date(Date.now()));
|
||||||
//check ad by looking for text banner
|
//check ad by looking for text banner
|
||||||
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
||||||
var foundAd = false;
|
var foundAd = false;
|
||||||
@ -904,9 +913,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(pollForAds,100);
|
//setTimeout(pollForAds,100);
|
||||||
}
|
}
|
||||||
function reloadTwitchPlayer() {
|
function pollForAdsObserver() {
|
||||||
|
pollForAds();
|
||||||
|
var vids = document.getElementsByClassName('video-player');
|
||||||
|
for (var i = 0; i < vids.length; i++) {
|
||||||
|
var observer = new MutationObserver(pollForAds);
|
||||||
|
observer.observe(vids[i], {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reloadTwitchPlayer(isPausePlay) {
|
||||||
// Taken from ttv-tools / ffz
|
// Taken from ttv-tools / ffz
|
||||||
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
||||||
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
||||||
@ -947,6 +969,11 @@
|
|||||||
if (player.paused) {
|
if (player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isPausePlay) {
|
||||||
|
player.pause();
|
||||||
|
player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
||||||
if (sink && sink.video && sink.video._ffz_compressor) {
|
if (sink && sink.video && sink.video._ffz_compressor) {
|
||||||
const video = sink.video;
|
const video = sink.video;
|
||||||
@ -974,11 +1001,11 @@
|
|||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
} else {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hookFetch();
|
hookFetch();
|
||||||
|
@ -49,8 +49,6 @@ twitch-videoad.js application/javascript
|
|||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.CurrentChannelNameFromM3U8 = null;
|
scope.CurrentChannelNameFromM3U8 = null;
|
||||||
scope.LastAdUrl = null;
|
|
||||||
scope.LastAdTime = 0;
|
|
||||||
// Need this in both scopes. Window scope needs to update this to worker scope.
|
// Need this in both scopes. Window scope needs to update this to worker scope.
|
||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
}
|
}
|
||||||
@ -82,6 +80,7 @@ twitch-videoad.js application/javascript
|
|||||||
${processM3U8.toString()}
|
${processM3U8.toString()}
|
||||||
${getSegmentInfos.toString()}
|
${getSegmentInfos.toString()}
|
||||||
${getSegmentInfosLines.toString()}
|
${getSegmentInfosLines.toString()}
|
||||||
|
${getFinalSegUrl.toString()}
|
||||||
${hookWorkerFetch.toString()}
|
${hookWorkerFetch.toString()}
|
||||||
${declareOptions.toString()}
|
${declareOptions.toString()}
|
||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
@ -121,6 +120,9 @@ twitch-videoad.js application/javascript
|
|||||||
else if (e.data.key == 'UboReloadPlayer') {
|
else if (e.data.key == 'UboReloadPlayer') {
|
||||||
reloadTwitchPlayer();
|
reloadTwitchPlayer();
|
||||||
}
|
}
|
||||||
|
else if (e.data.key == 'UboPauseResumePlayer') {
|
||||||
|
reloadTwitchPlayer(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getAdDiv() {
|
function getAdDiv() {
|
||||||
var playerRootDiv = document.querySelector('.video-player');
|
var playerRootDiv = document.querySelector('.video-player');
|
||||||
@ -196,7 +198,7 @@ twitch-videoad.js application/javascript
|
|||||||
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
||||||
segInfo.dateTimeLineIndex = i - 2;
|
segInfo.dateTimeLineIndex = i - 2;
|
||||||
segInfo.dateTimeLine = lines[i - 2];
|
segInfo.dateTimeLine = lines[i - 2];
|
||||||
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
|
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
|
||||||
}
|
}
|
||||||
result.segs.push(segInfo);
|
result.segs.push(segInfo);
|
||||||
}
|
}
|
||||||
@ -252,20 +254,42 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getFinalSegUrl(lines) {
|
||||||
|
for (var i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].startsWith("http")) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
||||||
var si = StreamInfosByUrl[url];
|
var si = StreamInfosByUrl[url];
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
si.BackupSeqNumber = -1;
|
|
||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
||||||
var oldRealSeq = si.RealSeqNumber;
|
var oldRealSeq = si.RealSeqNumber;
|
||||||
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
||||||
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
||||||
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
|
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
|
||||||
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
|
||||||
|
si.FakeSeqNumber = 0;
|
||||||
|
si.BackupSeqNumber = -1;
|
||||||
|
postMessage({key:'UboPauseResumePlayer'});*/
|
||||||
|
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
|
||||||
|
var finalSegUrl = getFinalSegUrl(lines);
|
||||||
|
if (finalSegUrl != si.FinalSegUrl) {
|
||||||
|
si.FinalSegUrl = finalSegUrl;
|
||||||
|
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
|
||||||
|
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
if (jump <= 3) {
|
||||||
|
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
} else if (jump > 0) {
|
||||||
|
si.FakeSeqNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
||||||
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
||||||
}
|
}
|
||||||
@ -314,8 +338,6 @@ twitch-videoad.js application/javascript
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (haveAdTags) {
|
if (haveAdTags) {
|
||||||
LastAdUrl = url;
|
|
||||||
LastAdTime = Date.now();
|
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
if (streamInfo == null) {
|
if (streamInfo == null) {
|
||||||
console.log('Unknown stream url ' + url);
|
console.log('Unknown stream url ' + url);
|
||||||
@ -364,33 +386,12 @@ twitch-videoad.js application/javascript
|
|||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
var newLines = [];
|
var newLines = [];
|
||||||
if (backupM3u8 != null) {
|
if (backupM3u8 != null) {
|
||||||
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
|
|
||||||
if (seqMatch != null) {
|
|
||||||
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
|
|
||||||
if (streamInfo.RealSeqNumber > 0) {
|
|
||||||
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
|
|
||||||
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
|
|
||||||
if (streamInfo.FakeSeqNumber == 0) {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
|
|
||||||
}
|
|
||||||
if (oldBackupSeqNumber == -1) {
|
|
||||||
// First backup sequence, assume +1
|
|
||||||
streamInfo.FakeSeqNumber++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
||||||
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
||||||
newLines.push('#EXTM3U');
|
newLines.push('#EXTM3U');
|
||||||
newLines.push('#EXT-X-VERSION:3');
|
newLines.push('#EXT-X-VERSION:3');
|
||||||
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
||||||
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
||||||
// The following will could cause issues when we stop stripping segments
|
// The following will could cause issues when we stop stripping segments
|
||||||
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
||||||
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
||||||
@ -418,6 +419,12 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finalSegUrl = getFinalSegUrl(newLines);
|
||||||
|
if (finalSegUrl != streamInfo.FinalSegUrl) {
|
||||||
|
streamInfo.FinalSegUrl = finalSegUrl;
|
||||||
|
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
|
||||||
|
}
|
||||||
|
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
|
||||||
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
||||||
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
||||||
} else {
|
} else {
|
||||||
@ -529,6 +536,7 @@ twitch-videoad.js application/javascript
|
|||||||
streamInfo.RealSeqNumber = -1;
|
streamInfo.RealSeqNumber = -1;
|
||||||
streamInfo.BackupSeqNumber = -1;
|
streamInfo.BackupSeqNumber = -1;
|
||||||
streamInfo.FakeSeqNumber = 0;
|
streamInfo.FakeSeqNumber = 0;
|
||||||
|
streamInfo.FinalSegUrl = null;
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
@ -860,6 +868,7 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pollForAds() {
|
function pollForAds() {
|
||||||
|
//console.log('pollForAds ' + new Date(Date.now()));
|
||||||
//check ad by looking for text banner
|
//check ad by looking for text banner
|
||||||
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
||||||
var foundAd = false;
|
var foundAd = false;
|
||||||
@ -893,9 +902,22 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(pollForAds,100);
|
//setTimeout(pollForAds,100);
|
||||||
}
|
}
|
||||||
function reloadTwitchPlayer() {
|
function pollForAdsObserver() {
|
||||||
|
pollForAds();
|
||||||
|
var vids = document.getElementsByClassName('video-player');
|
||||||
|
for (var i = 0; i < vids.length; i++) {
|
||||||
|
var observer = new MutationObserver(pollForAds);
|
||||||
|
observer.observe(vids[i], {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reloadTwitchPlayer(isPausePlay) {
|
||||||
// Taken from ttv-tools / ffz
|
// Taken from ttv-tools / ffz
|
||||||
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
||||||
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
||||||
@ -936,6 +958,11 @@ twitch-videoad.js application/javascript
|
|||||||
if (player.paused) {
|
if (player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isPausePlay) {
|
||||||
|
player.pause();
|
||||||
|
player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
||||||
if (sink && sink.video && sink.video._ffz_compressor) {
|
if (sink && sink.video && sink.video._ffz_compressor) {
|
||||||
const video = sink.video;
|
const video = sink.video;
|
||||||
@ -963,11 +990,11 @@ twitch-videoad.js application/javascript
|
|||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
} else {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hookFetch();
|
hookFetch();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name TwitchAdSolutions
|
// @name TwitchAdSolutions (notify-strip)
|
||||||
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
||||||
// @version 1.3
|
// @version 1.4
|
||||||
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip/notify-strip.user.js
|
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip/notify-strip.user.js
|
||||||
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip/notify-strip.user.js
|
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip/notify-strip.user.js
|
||||||
// @description Multiple solutions for blocking Twitch ads (notify-strip)
|
// @description Multiple solutions for blocking Twitch ads (notify-strip)
|
||||||
@ -60,8 +60,6 @@
|
|||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.CurrentChannelNameFromM3U8 = null;
|
scope.CurrentChannelNameFromM3U8 = null;
|
||||||
scope.LastAdUrl = null;
|
|
||||||
scope.LastAdTime = 0;
|
|
||||||
// Need this in both scopes. Window scope needs to update this to worker scope.
|
// Need this in both scopes. Window scope needs to update this to worker scope.
|
||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
}
|
}
|
||||||
@ -93,6 +91,7 @@
|
|||||||
${processM3U8.toString()}
|
${processM3U8.toString()}
|
||||||
${getSegmentInfos.toString()}
|
${getSegmentInfos.toString()}
|
||||||
${getSegmentInfosLines.toString()}
|
${getSegmentInfosLines.toString()}
|
||||||
|
${getFinalSegUrl.toString()}
|
||||||
${hookWorkerFetch.toString()}
|
${hookWorkerFetch.toString()}
|
||||||
${declareOptions.toString()}
|
${declareOptions.toString()}
|
||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
@ -132,6 +131,9 @@
|
|||||||
else if (e.data.key == 'UboReloadPlayer') {
|
else if (e.data.key == 'UboReloadPlayer') {
|
||||||
reloadTwitchPlayer();
|
reloadTwitchPlayer();
|
||||||
}
|
}
|
||||||
|
else if (e.data.key == 'UboPauseResumePlayer') {
|
||||||
|
reloadTwitchPlayer(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getAdDiv() {
|
function getAdDiv() {
|
||||||
var playerRootDiv = document.querySelector('.video-player');
|
var playerRootDiv = document.querySelector('.video-player');
|
||||||
@ -207,7 +209,7 @@
|
|||||||
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
||||||
segInfo.dateTimeLineIndex = i - 2;
|
segInfo.dateTimeLineIndex = i - 2;
|
||||||
segInfo.dateTimeLine = lines[i - 2];
|
segInfo.dateTimeLine = lines[i - 2];
|
||||||
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
|
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
|
||||||
}
|
}
|
||||||
result.segs.push(segInfo);
|
result.segs.push(segInfo);
|
||||||
}
|
}
|
||||||
@ -263,20 +265,42 @@
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getFinalSegUrl(lines) {
|
||||||
|
for (var i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].startsWith("http")) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
||||||
var si = StreamInfosByUrl[url];
|
var si = StreamInfosByUrl[url];
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
si.BackupSeqNumber = -1;
|
|
||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
||||||
var oldRealSeq = si.RealSeqNumber;
|
var oldRealSeq = si.RealSeqNumber;
|
||||||
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
||||||
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
||||||
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
|
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
|
||||||
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
|
||||||
|
si.FakeSeqNumber = 0;
|
||||||
|
si.BackupSeqNumber = -1;
|
||||||
|
postMessage({key:'UboPauseResumePlayer'});*/
|
||||||
|
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
|
||||||
|
var finalSegUrl = getFinalSegUrl(lines);
|
||||||
|
if (finalSegUrl != si.FinalSegUrl) {
|
||||||
|
si.FinalSegUrl = finalSegUrl;
|
||||||
|
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
|
||||||
|
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
if (jump <= 3) {
|
||||||
|
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
} else if (jump > 0) {
|
||||||
|
si.FakeSeqNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
||||||
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
||||||
}
|
}
|
||||||
@ -325,8 +349,6 @@
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (haveAdTags) {
|
if (haveAdTags) {
|
||||||
LastAdUrl = url;
|
|
||||||
LastAdTime = Date.now();
|
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
if (streamInfo == null) {
|
if (streamInfo == null) {
|
||||||
console.log('Unknown stream url ' + url);
|
console.log('Unknown stream url ' + url);
|
||||||
@ -375,33 +397,12 @@
|
|||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
var newLines = [];
|
var newLines = [];
|
||||||
if (backupM3u8 != null) {
|
if (backupM3u8 != null) {
|
||||||
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
|
|
||||||
if (seqMatch != null) {
|
|
||||||
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
|
|
||||||
if (streamInfo.RealSeqNumber > 0) {
|
|
||||||
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
|
|
||||||
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
|
|
||||||
if (streamInfo.FakeSeqNumber == 0) {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
|
|
||||||
}
|
|
||||||
if (oldBackupSeqNumber == -1) {
|
|
||||||
// First backup sequence, assume +1
|
|
||||||
streamInfo.FakeSeqNumber++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
||||||
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
||||||
newLines.push('#EXTM3U');
|
newLines.push('#EXTM3U');
|
||||||
newLines.push('#EXT-X-VERSION:3');
|
newLines.push('#EXT-X-VERSION:3');
|
||||||
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
||||||
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
||||||
// The following will could cause issues when we stop stripping segments
|
// The following will could cause issues when we stop stripping segments
|
||||||
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
||||||
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
||||||
@ -429,6 +430,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finalSegUrl = getFinalSegUrl(newLines);
|
||||||
|
if (finalSegUrl != streamInfo.FinalSegUrl) {
|
||||||
|
streamInfo.FinalSegUrl = finalSegUrl;
|
||||||
|
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
|
||||||
|
}
|
||||||
|
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
|
||||||
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
||||||
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
||||||
} else {
|
} else {
|
||||||
@ -540,6 +547,7 @@
|
|||||||
streamInfo.RealSeqNumber = -1;
|
streamInfo.RealSeqNumber = -1;
|
||||||
streamInfo.BackupSeqNumber = -1;
|
streamInfo.BackupSeqNumber = -1;
|
||||||
streamInfo.FakeSeqNumber = 0;
|
streamInfo.FakeSeqNumber = 0;
|
||||||
|
streamInfo.FinalSegUrl = null;
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
@ -871,6 +879,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pollForAds() {
|
function pollForAds() {
|
||||||
|
//console.log('pollForAds ' + new Date(Date.now()));
|
||||||
//check ad by looking for text banner
|
//check ad by looking for text banner
|
||||||
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
||||||
var foundAd = false;
|
var foundAd = false;
|
||||||
@ -904,9 +913,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(pollForAds,100);
|
//setTimeout(pollForAds,100);
|
||||||
}
|
}
|
||||||
function reloadTwitchPlayer() {
|
function pollForAdsObserver() {
|
||||||
|
pollForAds();
|
||||||
|
var vids = document.getElementsByClassName('video-player');
|
||||||
|
for (var i = 0; i < vids.length; i++) {
|
||||||
|
var observer = new MutationObserver(pollForAds);
|
||||||
|
observer.observe(vids[i], {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reloadTwitchPlayer(isPausePlay) {
|
||||||
// Taken from ttv-tools / ffz
|
// Taken from ttv-tools / ffz
|
||||||
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
||||||
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
||||||
@ -947,6 +969,11 @@
|
|||||||
if (player.paused) {
|
if (player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isPausePlay) {
|
||||||
|
player.pause();
|
||||||
|
player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
||||||
if (sink && sink.video && sink.video._ffz_compressor) {
|
if (sink && sink.video && sink.video._ffz_compressor) {
|
||||||
const video = sink.video;
|
const video = sink.video;
|
||||||
@ -974,11 +1001,11 @@
|
|||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
} else {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hookFetch();
|
hookFetch();
|
||||||
|
@ -49,8 +49,6 @@ twitch-videoad.js application/javascript
|
|||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.CurrentChannelNameFromM3U8 = null;
|
scope.CurrentChannelNameFromM3U8 = null;
|
||||||
scope.LastAdUrl = null;
|
|
||||||
scope.LastAdTime = 0;
|
|
||||||
// Need this in both scopes. Window scope needs to update this to worker scope.
|
// Need this in both scopes. Window scope needs to update this to worker scope.
|
||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
}
|
}
|
||||||
@ -82,6 +80,7 @@ twitch-videoad.js application/javascript
|
|||||||
${processM3U8.toString()}
|
${processM3U8.toString()}
|
||||||
${getSegmentInfos.toString()}
|
${getSegmentInfos.toString()}
|
||||||
${getSegmentInfosLines.toString()}
|
${getSegmentInfosLines.toString()}
|
||||||
|
${getFinalSegUrl.toString()}
|
||||||
${hookWorkerFetch.toString()}
|
${hookWorkerFetch.toString()}
|
||||||
${declareOptions.toString()}
|
${declareOptions.toString()}
|
||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
@ -121,6 +120,9 @@ twitch-videoad.js application/javascript
|
|||||||
else if (e.data.key == 'UboReloadPlayer') {
|
else if (e.data.key == 'UboReloadPlayer') {
|
||||||
reloadTwitchPlayer();
|
reloadTwitchPlayer();
|
||||||
}
|
}
|
||||||
|
else if (e.data.key == 'UboPauseResumePlayer') {
|
||||||
|
reloadTwitchPlayer(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getAdDiv() {
|
function getAdDiv() {
|
||||||
var playerRootDiv = document.querySelector('.video-player');
|
var playerRootDiv = document.querySelector('.video-player');
|
||||||
@ -196,7 +198,7 @@ twitch-videoad.js application/javascript
|
|||||||
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
||||||
segInfo.dateTimeLineIndex = i - 2;
|
segInfo.dateTimeLineIndex = i - 2;
|
||||||
segInfo.dateTimeLine = lines[i - 2];
|
segInfo.dateTimeLine = lines[i - 2];
|
||||||
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
|
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
|
||||||
}
|
}
|
||||||
result.segs.push(segInfo);
|
result.segs.push(segInfo);
|
||||||
}
|
}
|
||||||
@ -252,20 +254,42 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getFinalSegUrl(lines) {
|
||||||
|
for (var i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].startsWith("http")) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
||||||
var si = StreamInfosByUrl[url];
|
var si = StreamInfosByUrl[url];
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
si.BackupSeqNumber = -1;
|
|
||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
||||||
var oldRealSeq = si.RealSeqNumber;
|
var oldRealSeq = si.RealSeqNumber;
|
||||||
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
||||||
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
||||||
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
|
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
|
||||||
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
|
||||||
|
si.FakeSeqNumber = 0;
|
||||||
|
si.BackupSeqNumber = -1;
|
||||||
|
postMessage({key:'UboPauseResumePlayer'});*/
|
||||||
|
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
|
||||||
|
var finalSegUrl = getFinalSegUrl(lines);
|
||||||
|
if (finalSegUrl != si.FinalSegUrl) {
|
||||||
|
si.FinalSegUrl = finalSegUrl;
|
||||||
|
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
|
||||||
|
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
if (jump <= 3) {
|
||||||
|
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
} else if (jump > 0) {
|
||||||
|
si.FakeSeqNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
||||||
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
||||||
}
|
}
|
||||||
@ -314,8 +338,6 @@ twitch-videoad.js application/javascript
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (haveAdTags) {
|
if (haveAdTags) {
|
||||||
LastAdUrl = url;
|
|
||||||
LastAdTime = Date.now();
|
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
if (streamInfo == null) {
|
if (streamInfo == null) {
|
||||||
console.log('Unknown stream url ' + url);
|
console.log('Unknown stream url ' + url);
|
||||||
@ -364,33 +386,12 @@ twitch-videoad.js application/javascript
|
|||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
var newLines = [];
|
var newLines = [];
|
||||||
if (backupM3u8 != null) {
|
if (backupM3u8 != null) {
|
||||||
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
|
|
||||||
if (seqMatch != null) {
|
|
||||||
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
|
|
||||||
if (streamInfo.RealSeqNumber > 0) {
|
|
||||||
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
|
|
||||||
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
|
|
||||||
if (streamInfo.FakeSeqNumber == 0) {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
|
|
||||||
}
|
|
||||||
if (oldBackupSeqNumber == -1) {
|
|
||||||
// First backup sequence, assume +1
|
|
||||||
streamInfo.FakeSeqNumber++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
||||||
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
||||||
newLines.push('#EXTM3U');
|
newLines.push('#EXTM3U');
|
||||||
newLines.push('#EXT-X-VERSION:3');
|
newLines.push('#EXT-X-VERSION:3');
|
||||||
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
||||||
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
||||||
// The following will could cause issues when we stop stripping segments
|
// The following will could cause issues when we stop stripping segments
|
||||||
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
||||||
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
||||||
@ -418,6 +419,12 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finalSegUrl = getFinalSegUrl(newLines);
|
||||||
|
if (finalSegUrl != streamInfo.FinalSegUrl) {
|
||||||
|
streamInfo.FinalSegUrl = finalSegUrl;
|
||||||
|
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
|
||||||
|
}
|
||||||
|
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
|
||||||
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
||||||
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
||||||
} else {
|
} else {
|
||||||
@ -529,6 +536,7 @@ twitch-videoad.js application/javascript
|
|||||||
streamInfo.RealSeqNumber = -1;
|
streamInfo.RealSeqNumber = -1;
|
||||||
streamInfo.BackupSeqNumber = -1;
|
streamInfo.BackupSeqNumber = -1;
|
||||||
streamInfo.FakeSeqNumber = 0;
|
streamInfo.FakeSeqNumber = 0;
|
||||||
|
streamInfo.FinalSegUrl = null;
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
@ -860,6 +868,7 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pollForAds() {
|
function pollForAds() {
|
||||||
|
//console.log('pollForAds ' + new Date(Date.now()));
|
||||||
//check ad by looking for text banner
|
//check ad by looking for text banner
|
||||||
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
||||||
var foundAd = false;
|
var foundAd = false;
|
||||||
@ -893,9 +902,22 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(pollForAds,100);
|
//setTimeout(pollForAds,100);
|
||||||
}
|
}
|
||||||
function reloadTwitchPlayer() {
|
function pollForAdsObserver() {
|
||||||
|
pollForAds();
|
||||||
|
var vids = document.getElementsByClassName('video-player');
|
||||||
|
for (var i = 0; i < vids.length; i++) {
|
||||||
|
var observer = new MutationObserver(pollForAds);
|
||||||
|
observer.observe(vids[i], {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reloadTwitchPlayer(isPausePlay) {
|
||||||
// Taken from ttv-tools / ffz
|
// Taken from ttv-tools / ffz
|
||||||
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
||||||
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
||||||
@ -936,6 +958,11 @@ twitch-videoad.js application/javascript
|
|||||||
if (player.paused) {
|
if (player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isPausePlay) {
|
||||||
|
player.pause();
|
||||||
|
player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
||||||
if (sink && sink.video && sink.video._ffz_compressor) {
|
if (sink && sink.video && sink.video._ffz_compressor) {
|
||||||
const video = sink.video;
|
const video = sink.video;
|
||||||
@ -963,11 +990,11 @@ twitch-videoad.js application/javascript
|
|||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
} else {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hookFetch();
|
hookFetch();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name TwitchAdSolutions
|
// @name TwitchAdSolutions (proxy-m3u8)
|
||||||
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
||||||
// @version 1.3
|
// @version 1.4
|
||||||
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/proxy-m3u8/proxy-m3u8.user.js
|
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/proxy-m3u8/proxy-m3u8.user.js
|
||||||
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/proxy-m3u8/proxy-m3u8.user.js
|
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/proxy-m3u8/proxy-m3u8.user.js
|
||||||
// @description Multiple solutions for blocking Twitch ads (proxy-m3u8)
|
// @description Multiple solutions for blocking Twitch ads (proxy-m3u8)
|
||||||
@ -60,8 +60,6 @@
|
|||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.CurrentChannelNameFromM3U8 = null;
|
scope.CurrentChannelNameFromM3U8 = null;
|
||||||
scope.LastAdUrl = null;
|
|
||||||
scope.LastAdTime = 0;
|
|
||||||
// Need this in both scopes. Window scope needs to update this to worker scope.
|
// Need this in both scopes. Window scope needs to update this to worker scope.
|
||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
}
|
}
|
||||||
@ -93,6 +91,7 @@
|
|||||||
${processM3U8.toString()}
|
${processM3U8.toString()}
|
||||||
${getSegmentInfos.toString()}
|
${getSegmentInfos.toString()}
|
||||||
${getSegmentInfosLines.toString()}
|
${getSegmentInfosLines.toString()}
|
||||||
|
${getFinalSegUrl.toString()}
|
||||||
${hookWorkerFetch.toString()}
|
${hookWorkerFetch.toString()}
|
||||||
${declareOptions.toString()}
|
${declareOptions.toString()}
|
||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
@ -132,6 +131,9 @@
|
|||||||
else if (e.data.key == 'UboReloadPlayer') {
|
else if (e.data.key == 'UboReloadPlayer') {
|
||||||
reloadTwitchPlayer();
|
reloadTwitchPlayer();
|
||||||
}
|
}
|
||||||
|
else if (e.data.key == 'UboPauseResumePlayer') {
|
||||||
|
reloadTwitchPlayer(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getAdDiv() {
|
function getAdDiv() {
|
||||||
var playerRootDiv = document.querySelector('.video-player');
|
var playerRootDiv = document.querySelector('.video-player');
|
||||||
@ -207,7 +209,7 @@
|
|||||||
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
||||||
segInfo.dateTimeLineIndex = i - 2;
|
segInfo.dateTimeLineIndex = i - 2;
|
||||||
segInfo.dateTimeLine = lines[i - 2];
|
segInfo.dateTimeLine = lines[i - 2];
|
||||||
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
|
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
|
||||||
}
|
}
|
||||||
result.segs.push(segInfo);
|
result.segs.push(segInfo);
|
||||||
}
|
}
|
||||||
@ -263,20 +265,42 @@
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getFinalSegUrl(lines) {
|
||||||
|
for (var i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].startsWith("http")) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
||||||
var si = StreamInfosByUrl[url];
|
var si = StreamInfosByUrl[url];
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
si.BackupSeqNumber = -1;
|
|
||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
||||||
var oldRealSeq = si.RealSeqNumber;
|
var oldRealSeq = si.RealSeqNumber;
|
||||||
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
||||||
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
||||||
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
|
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
|
||||||
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
|
||||||
|
si.FakeSeqNumber = 0;
|
||||||
|
si.BackupSeqNumber = -1;
|
||||||
|
postMessage({key:'UboPauseResumePlayer'});*/
|
||||||
|
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
|
||||||
|
var finalSegUrl = getFinalSegUrl(lines);
|
||||||
|
if (finalSegUrl != si.FinalSegUrl) {
|
||||||
|
si.FinalSegUrl = finalSegUrl;
|
||||||
|
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
|
||||||
|
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
if (jump <= 3) {
|
||||||
|
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
} else if (jump > 0) {
|
||||||
|
si.FakeSeqNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
||||||
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
||||||
}
|
}
|
||||||
@ -325,8 +349,6 @@
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (haveAdTags) {
|
if (haveAdTags) {
|
||||||
LastAdUrl = url;
|
|
||||||
LastAdTime = Date.now();
|
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
if (streamInfo == null) {
|
if (streamInfo == null) {
|
||||||
console.log('Unknown stream url ' + url);
|
console.log('Unknown stream url ' + url);
|
||||||
@ -375,33 +397,12 @@
|
|||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
var newLines = [];
|
var newLines = [];
|
||||||
if (backupM3u8 != null) {
|
if (backupM3u8 != null) {
|
||||||
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
|
|
||||||
if (seqMatch != null) {
|
|
||||||
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
|
|
||||||
if (streamInfo.RealSeqNumber > 0) {
|
|
||||||
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
|
|
||||||
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
|
|
||||||
if (streamInfo.FakeSeqNumber == 0) {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
|
|
||||||
}
|
|
||||||
if (oldBackupSeqNumber == -1) {
|
|
||||||
// First backup sequence, assume +1
|
|
||||||
streamInfo.FakeSeqNumber++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
||||||
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
||||||
newLines.push('#EXTM3U');
|
newLines.push('#EXTM3U');
|
||||||
newLines.push('#EXT-X-VERSION:3');
|
newLines.push('#EXT-X-VERSION:3');
|
||||||
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
||||||
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
||||||
// The following will could cause issues when we stop stripping segments
|
// The following will could cause issues when we stop stripping segments
|
||||||
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
||||||
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
||||||
@ -429,6 +430,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finalSegUrl = getFinalSegUrl(newLines);
|
||||||
|
if (finalSegUrl != streamInfo.FinalSegUrl) {
|
||||||
|
streamInfo.FinalSegUrl = finalSegUrl;
|
||||||
|
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
|
||||||
|
}
|
||||||
|
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
|
||||||
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
||||||
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
||||||
} else {
|
} else {
|
||||||
@ -540,6 +547,7 @@
|
|||||||
streamInfo.RealSeqNumber = -1;
|
streamInfo.RealSeqNumber = -1;
|
||||||
streamInfo.BackupSeqNumber = -1;
|
streamInfo.BackupSeqNumber = -1;
|
||||||
streamInfo.FakeSeqNumber = 0;
|
streamInfo.FakeSeqNumber = 0;
|
||||||
|
streamInfo.FinalSegUrl = null;
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
@ -871,6 +879,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pollForAds() {
|
function pollForAds() {
|
||||||
|
//console.log('pollForAds ' + new Date(Date.now()));
|
||||||
//check ad by looking for text banner
|
//check ad by looking for text banner
|
||||||
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
||||||
var foundAd = false;
|
var foundAd = false;
|
||||||
@ -904,9 +913,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(pollForAds,100);
|
//setTimeout(pollForAds,100);
|
||||||
}
|
}
|
||||||
function reloadTwitchPlayer() {
|
function pollForAdsObserver() {
|
||||||
|
pollForAds();
|
||||||
|
var vids = document.getElementsByClassName('video-player');
|
||||||
|
for (var i = 0; i < vids.length; i++) {
|
||||||
|
var observer = new MutationObserver(pollForAds);
|
||||||
|
observer.observe(vids[i], {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reloadTwitchPlayer(isPausePlay) {
|
||||||
// Taken from ttv-tools / ffz
|
// Taken from ttv-tools / ffz
|
||||||
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
||||||
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
||||||
@ -947,6 +969,11 @@
|
|||||||
if (player.paused) {
|
if (player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isPausePlay) {
|
||||||
|
player.pause();
|
||||||
|
player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
||||||
if (sink && sink.video && sink.video._ffz_compressor) {
|
if (sink && sink.video && sink.video._ffz_compressor) {
|
||||||
const video = sink.video;
|
const video = sink.video;
|
||||||
@ -974,11 +1001,11 @@
|
|||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
} else {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hookFetch();
|
hookFetch();
|
||||||
|
@ -49,8 +49,6 @@ twitch-videoad.js application/javascript
|
|||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.CurrentChannelNameFromM3U8 = null;
|
scope.CurrentChannelNameFromM3U8 = null;
|
||||||
scope.LastAdUrl = null;
|
|
||||||
scope.LastAdTime = 0;
|
|
||||||
// Need this in both scopes. Window scope needs to update this to worker scope.
|
// Need this in both scopes. Window scope needs to update this to worker scope.
|
||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
}
|
}
|
||||||
@ -82,6 +80,7 @@ twitch-videoad.js application/javascript
|
|||||||
${processM3U8.toString()}
|
${processM3U8.toString()}
|
||||||
${getSegmentInfos.toString()}
|
${getSegmentInfos.toString()}
|
||||||
${getSegmentInfosLines.toString()}
|
${getSegmentInfosLines.toString()}
|
||||||
|
${getFinalSegUrl.toString()}
|
||||||
${hookWorkerFetch.toString()}
|
${hookWorkerFetch.toString()}
|
||||||
${declareOptions.toString()}
|
${declareOptions.toString()}
|
||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
@ -121,6 +120,9 @@ twitch-videoad.js application/javascript
|
|||||||
else if (e.data.key == 'UboReloadPlayer') {
|
else if (e.data.key == 'UboReloadPlayer') {
|
||||||
reloadTwitchPlayer();
|
reloadTwitchPlayer();
|
||||||
}
|
}
|
||||||
|
else if (e.data.key == 'UboPauseResumePlayer') {
|
||||||
|
reloadTwitchPlayer(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getAdDiv() {
|
function getAdDiv() {
|
||||||
var playerRootDiv = document.querySelector('.video-player');
|
var playerRootDiv = document.querySelector('.video-player');
|
||||||
@ -196,7 +198,7 @@ twitch-videoad.js application/javascript
|
|||||||
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
||||||
segInfo.dateTimeLineIndex = i - 2;
|
segInfo.dateTimeLineIndex = i - 2;
|
||||||
segInfo.dateTimeLine = lines[i - 2];
|
segInfo.dateTimeLine = lines[i - 2];
|
||||||
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
|
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
|
||||||
}
|
}
|
||||||
result.segs.push(segInfo);
|
result.segs.push(segInfo);
|
||||||
}
|
}
|
||||||
@ -252,20 +254,42 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getFinalSegUrl(lines) {
|
||||||
|
for (var i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].startsWith("http")) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
||||||
var si = StreamInfosByUrl[url];
|
var si = StreamInfosByUrl[url];
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
si.BackupSeqNumber = -1;
|
|
||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
||||||
var oldRealSeq = si.RealSeqNumber;
|
var oldRealSeq = si.RealSeqNumber;
|
||||||
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
||||||
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
||||||
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
|
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
|
||||||
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
|
||||||
|
si.FakeSeqNumber = 0;
|
||||||
|
si.BackupSeqNumber = -1;
|
||||||
|
postMessage({key:'UboPauseResumePlayer'});*/
|
||||||
|
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
|
||||||
|
var finalSegUrl = getFinalSegUrl(lines);
|
||||||
|
if (finalSegUrl != si.FinalSegUrl) {
|
||||||
|
si.FinalSegUrl = finalSegUrl;
|
||||||
|
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
|
||||||
|
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
if (jump <= 3) {
|
||||||
|
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
} else if (jump > 0) {
|
||||||
|
si.FakeSeqNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
||||||
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
||||||
}
|
}
|
||||||
@ -314,8 +338,6 @@ twitch-videoad.js application/javascript
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (haveAdTags) {
|
if (haveAdTags) {
|
||||||
LastAdUrl = url;
|
|
||||||
LastAdTime = Date.now();
|
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
if (streamInfo == null) {
|
if (streamInfo == null) {
|
||||||
console.log('Unknown stream url ' + url);
|
console.log('Unknown stream url ' + url);
|
||||||
@ -364,33 +386,12 @@ twitch-videoad.js application/javascript
|
|||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
var newLines = [];
|
var newLines = [];
|
||||||
if (backupM3u8 != null) {
|
if (backupM3u8 != null) {
|
||||||
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
|
|
||||||
if (seqMatch != null) {
|
|
||||||
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
|
|
||||||
if (streamInfo.RealSeqNumber > 0) {
|
|
||||||
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
|
|
||||||
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
|
|
||||||
if (streamInfo.FakeSeqNumber == 0) {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
|
|
||||||
}
|
|
||||||
if (oldBackupSeqNumber == -1) {
|
|
||||||
// First backup sequence, assume +1
|
|
||||||
streamInfo.FakeSeqNumber++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
||||||
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
||||||
newLines.push('#EXTM3U');
|
newLines.push('#EXTM3U');
|
||||||
newLines.push('#EXT-X-VERSION:3');
|
newLines.push('#EXT-X-VERSION:3');
|
||||||
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
||||||
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
||||||
// The following will could cause issues when we stop stripping segments
|
// The following will could cause issues when we stop stripping segments
|
||||||
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
||||||
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
||||||
@ -418,6 +419,12 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finalSegUrl = getFinalSegUrl(newLines);
|
||||||
|
if (finalSegUrl != streamInfo.FinalSegUrl) {
|
||||||
|
streamInfo.FinalSegUrl = finalSegUrl;
|
||||||
|
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
|
||||||
|
}
|
||||||
|
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
|
||||||
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
||||||
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
||||||
} else {
|
} else {
|
||||||
@ -529,6 +536,7 @@ twitch-videoad.js application/javascript
|
|||||||
streamInfo.RealSeqNumber = -1;
|
streamInfo.RealSeqNumber = -1;
|
||||||
streamInfo.BackupSeqNumber = -1;
|
streamInfo.BackupSeqNumber = -1;
|
||||||
streamInfo.FakeSeqNumber = 0;
|
streamInfo.FakeSeqNumber = 0;
|
||||||
|
streamInfo.FinalSegUrl = null;
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
@ -860,6 +868,7 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pollForAds() {
|
function pollForAds() {
|
||||||
|
//console.log('pollForAds ' + new Date(Date.now()));
|
||||||
//check ad by looking for text banner
|
//check ad by looking for text banner
|
||||||
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
||||||
var foundAd = false;
|
var foundAd = false;
|
||||||
@ -893,9 +902,22 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(pollForAds,100);
|
//setTimeout(pollForAds,100);
|
||||||
}
|
}
|
||||||
function reloadTwitchPlayer() {
|
function pollForAdsObserver() {
|
||||||
|
pollForAds();
|
||||||
|
var vids = document.getElementsByClassName('video-player');
|
||||||
|
for (var i = 0; i < vids.length; i++) {
|
||||||
|
var observer = new MutationObserver(pollForAds);
|
||||||
|
observer.observe(vids[i], {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reloadTwitchPlayer(isPausePlay) {
|
||||||
// Taken from ttv-tools / ffz
|
// Taken from ttv-tools / ffz
|
||||||
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
||||||
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
||||||
@ -936,6 +958,11 @@ twitch-videoad.js application/javascript
|
|||||||
if (player.paused) {
|
if (player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isPausePlay) {
|
||||||
|
player.pause();
|
||||||
|
player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
||||||
if (sink && sink.video && sink.video._ffz_compressor) {
|
if (sink && sink.video && sink.video._ffz_compressor) {
|
||||||
const video = sink.video;
|
const video = sink.video;
|
||||||
@ -963,11 +990,11 @@ twitch-videoad.js application/javascript
|
|||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
} else {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hookFetch();
|
hookFetch();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name TwitchAdSolutions
|
// @name TwitchAdSolutions (strip)
|
||||||
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
||||||
// @version 1.3
|
// @version 1.4
|
||||||
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/strip/strip.user.js
|
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/strip/strip.user.js
|
||||||
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/strip/strip.user.js
|
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/strip/strip.user.js
|
||||||
// @description Multiple solutions for blocking Twitch ads (strip)
|
// @description Multiple solutions for blocking Twitch ads (strip)
|
||||||
@ -60,8 +60,6 @@
|
|||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.CurrentChannelNameFromM3U8 = null;
|
scope.CurrentChannelNameFromM3U8 = null;
|
||||||
scope.LastAdUrl = null;
|
|
||||||
scope.LastAdTime = 0;
|
|
||||||
// Need this in both scopes. Window scope needs to update this to worker scope.
|
// Need this in both scopes. Window scope needs to update this to worker scope.
|
||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
}
|
}
|
||||||
@ -93,6 +91,7 @@
|
|||||||
${processM3U8.toString()}
|
${processM3U8.toString()}
|
||||||
${getSegmentInfos.toString()}
|
${getSegmentInfos.toString()}
|
||||||
${getSegmentInfosLines.toString()}
|
${getSegmentInfosLines.toString()}
|
||||||
|
${getFinalSegUrl.toString()}
|
||||||
${hookWorkerFetch.toString()}
|
${hookWorkerFetch.toString()}
|
||||||
${declareOptions.toString()}
|
${declareOptions.toString()}
|
||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
@ -132,6 +131,9 @@
|
|||||||
else if (e.data.key == 'UboReloadPlayer') {
|
else if (e.data.key == 'UboReloadPlayer') {
|
||||||
reloadTwitchPlayer();
|
reloadTwitchPlayer();
|
||||||
}
|
}
|
||||||
|
else if (e.data.key == 'UboPauseResumePlayer') {
|
||||||
|
reloadTwitchPlayer(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getAdDiv() {
|
function getAdDiv() {
|
||||||
var playerRootDiv = document.querySelector('.video-player');
|
var playerRootDiv = document.querySelector('.video-player');
|
||||||
@ -207,7 +209,7 @@
|
|||||||
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
||||||
segInfo.dateTimeLineIndex = i - 2;
|
segInfo.dateTimeLineIndex = i - 2;
|
||||||
segInfo.dateTimeLine = lines[i - 2];
|
segInfo.dateTimeLine = lines[i - 2];
|
||||||
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
|
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
|
||||||
}
|
}
|
||||||
result.segs.push(segInfo);
|
result.segs.push(segInfo);
|
||||||
}
|
}
|
||||||
@ -263,20 +265,42 @@
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getFinalSegUrl(lines) {
|
||||||
|
for (var i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].startsWith("http")) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
||||||
var si = StreamInfosByUrl[url];
|
var si = StreamInfosByUrl[url];
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
si.BackupSeqNumber = -1;
|
|
||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
||||||
var oldRealSeq = si.RealSeqNumber;
|
var oldRealSeq = si.RealSeqNumber;
|
||||||
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
||||||
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
||||||
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
|
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
|
||||||
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
|
||||||
|
si.FakeSeqNumber = 0;
|
||||||
|
si.BackupSeqNumber = -1;
|
||||||
|
postMessage({key:'UboPauseResumePlayer'});*/
|
||||||
|
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
|
||||||
|
var finalSegUrl = getFinalSegUrl(lines);
|
||||||
|
if (finalSegUrl != si.FinalSegUrl) {
|
||||||
|
si.FinalSegUrl = finalSegUrl;
|
||||||
|
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
|
||||||
|
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
if (jump <= 3) {
|
||||||
|
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
} else if (jump > 0) {
|
||||||
|
si.FakeSeqNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
||||||
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
||||||
}
|
}
|
||||||
@ -325,8 +349,6 @@
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (haveAdTags) {
|
if (haveAdTags) {
|
||||||
LastAdUrl = url;
|
|
||||||
LastAdTime = Date.now();
|
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
if (streamInfo == null) {
|
if (streamInfo == null) {
|
||||||
console.log('Unknown stream url ' + url);
|
console.log('Unknown stream url ' + url);
|
||||||
@ -375,33 +397,12 @@
|
|||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
var newLines = [];
|
var newLines = [];
|
||||||
if (backupM3u8 != null) {
|
if (backupM3u8 != null) {
|
||||||
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
|
|
||||||
if (seqMatch != null) {
|
|
||||||
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
|
|
||||||
if (streamInfo.RealSeqNumber > 0) {
|
|
||||||
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
|
|
||||||
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
|
|
||||||
if (streamInfo.FakeSeqNumber == 0) {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
|
|
||||||
}
|
|
||||||
if (oldBackupSeqNumber == -1) {
|
|
||||||
// First backup sequence, assume +1
|
|
||||||
streamInfo.FakeSeqNumber++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
||||||
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
||||||
newLines.push('#EXTM3U');
|
newLines.push('#EXTM3U');
|
||||||
newLines.push('#EXT-X-VERSION:3');
|
newLines.push('#EXT-X-VERSION:3');
|
||||||
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
||||||
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
||||||
// The following will could cause issues when we stop stripping segments
|
// The following will could cause issues when we stop stripping segments
|
||||||
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
||||||
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
||||||
@ -429,6 +430,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finalSegUrl = getFinalSegUrl(newLines);
|
||||||
|
if (finalSegUrl != streamInfo.FinalSegUrl) {
|
||||||
|
streamInfo.FinalSegUrl = finalSegUrl;
|
||||||
|
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
|
||||||
|
}
|
||||||
|
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
|
||||||
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
||||||
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
||||||
} else {
|
} else {
|
||||||
@ -540,6 +547,7 @@
|
|||||||
streamInfo.RealSeqNumber = -1;
|
streamInfo.RealSeqNumber = -1;
|
||||||
streamInfo.BackupSeqNumber = -1;
|
streamInfo.BackupSeqNumber = -1;
|
||||||
streamInfo.FakeSeqNumber = 0;
|
streamInfo.FakeSeqNumber = 0;
|
||||||
|
streamInfo.FinalSegUrl = null;
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
@ -871,6 +879,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pollForAds() {
|
function pollForAds() {
|
||||||
|
//console.log('pollForAds ' + new Date(Date.now()));
|
||||||
//check ad by looking for text banner
|
//check ad by looking for text banner
|
||||||
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
||||||
var foundAd = false;
|
var foundAd = false;
|
||||||
@ -904,9 +913,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(pollForAds,100);
|
//setTimeout(pollForAds,100);
|
||||||
}
|
}
|
||||||
function reloadTwitchPlayer() {
|
function pollForAdsObserver() {
|
||||||
|
pollForAds();
|
||||||
|
var vids = document.getElementsByClassName('video-player');
|
||||||
|
for (var i = 0; i < vids.length; i++) {
|
||||||
|
var observer = new MutationObserver(pollForAds);
|
||||||
|
observer.observe(vids[i], {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reloadTwitchPlayer(isPausePlay) {
|
||||||
// Taken from ttv-tools / ffz
|
// Taken from ttv-tools / ffz
|
||||||
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
||||||
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
||||||
@ -947,6 +969,11 @@
|
|||||||
if (player.paused) {
|
if (player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isPausePlay) {
|
||||||
|
player.pause();
|
||||||
|
player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
||||||
if (sink && sink.video && sink.video._ffz_compressor) {
|
if (sink && sink.video && sink.video._ffz_compressor) {
|
||||||
const video = sink.video;
|
const video = sink.video;
|
||||||
@ -974,11 +1001,11 @@
|
|||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
} else {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hookFetch();
|
hookFetch();
|
||||||
|
4
utils.cs
4
utils.cs
@ -126,6 +126,10 @@ namespace TwitchAdUtils
|
|||||||
{
|
{
|
||||||
modifiedOptions = true;
|
modifiedOptions = true;
|
||||||
}
|
}
|
||||||
|
if (lineTrimmed.StartsWith("// @name "))
|
||||||
|
{
|
||||||
|
line = line += " (" + dirInfo.Name + ")";
|
||||||
|
}
|
||||||
if (lineTrimmed.StartsWith("// @description"))
|
if (lineTrimmed.StartsWith("// @description"))
|
||||||
{
|
{
|
||||||
string url = "https://github.com/pixeltris/TwitchAdSolutions/raw/master/" + dirInfo.Name + "/" + dirInfo.Name + suffixUserscript;
|
string url = "https://github.com/pixeltris/TwitchAdSolutions/raw/master/" + dirInfo.Name + "/" + dirInfo.Name + suffixUserscript;
|
||||||
|
@ -49,8 +49,6 @@ twitch-videoad.js application/javascript
|
|||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.CurrentChannelNameFromM3U8 = null;
|
scope.CurrentChannelNameFromM3U8 = null;
|
||||||
scope.LastAdUrl = null;
|
|
||||||
scope.LastAdTime = 0;
|
|
||||||
// Need this in both scopes. Window scope needs to update this to worker scope.
|
// Need this in both scopes. Window scope needs to update this to worker scope.
|
||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
}
|
}
|
||||||
@ -82,6 +80,7 @@ twitch-videoad.js application/javascript
|
|||||||
${processM3U8.toString()}
|
${processM3U8.toString()}
|
||||||
${getSegmentInfos.toString()}
|
${getSegmentInfos.toString()}
|
||||||
${getSegmentInfosLines.toString()}
|
${getSegmentInfosLines.toString()}
|
||||||
|
${getFinalSegUrl.toString()}
|
||||||
${hookWorkerFetch.toString()}
|
${hookWorkerFetch.toString()}
|
||||||
${declareOptions.toString()}
|
${declareOptions.toString()}
|
||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
@ -121,6 +120,9 @@ twitch-videoad.js application/javascript
|
|||||||
else if (e.data.key == 'UboReloadPlayer') {
|
else if (e.data.key == 'UboReloadPlayer') {
|
||||||
reloadTwitchPlayer();
|
reloadTwitchPlayer();
|
||||||
}
|
}
|
||||||
|
else if (e.data.key == 'UboPauseResumePlayer') {
|
||||||
|
reloadTwitchPlayer(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getAdDiv() {
|
function getAdDiv() {
|
||||||
var playerRootDiv = document.querySelector('.video-player');
|
var playerRootDiv = document.querySelector('.video-player');
|
||||||
@ -196,7 +198,7 @@ twitch-videoad.js application/javascript
|
|||||||
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
||||||
segInfo.dateTimeLineIndex = i - 2;
|
segInfo.dateTimeLineIndex = i - 2;
|
||||||
segInfo.dateTimeLine = lines[i - 2];
|
segInfo.dateTimeLine = lines[i - 2];
|
||||||
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
|
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
|
||||||
}
|
}
|
||||||
result.segs.push(segInfo);
|
result.segs.push(segInfo);
|
||||||
}
|
}
|
||||||
@ -252,20 +254,42 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getFinalSegUrl(lines) {
|
||||||
|
for (var i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].startsWith("http")) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
||||||
var si = StreamInfosByUrl[url];
|
var si = StreamInfosByUrl[url];
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
si.BackupSeqNumber = -1;
|
|
||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
||||||
var oldRealSeq = si.RealSeqNumber;
|
var oldRealSeq = si.RealSeqNumber;
|
||||||
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
||||||
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
||||||
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
|
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
|
||||||
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
|
||||||
|
si.FakeSeqNumber = 0;
|
||||||
|
si.BackupSeqNumber = -1;
|
||||||
|
postMessage({key:'UboPauseResumePlayer'});*/
|
||||||
|
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
|
||||||
|
var finalSegUrl = getFinalSegUrl(lines);
|
||||||
|
if (finalSegUrl != si.FinalSegUrl) {
|
||||||
|
si.FinalSegUrl = finalSegUrl;
|
||||||
|
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
|
||||||
|
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
if (jump <= 3) {
|
||||||
|
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
} else if (jump > 0) {
|
||||||
|
si.FakeSeqNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
||||||
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
||||||
}
|
}
|
||||||
@ -314,8 +338,6 @@ twitch-videoad.js application/javascript
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (haveAdTags) {
|
if (haveAdTags) {
|
||||||
LastAdUrl = url;
|
|
||||||
LastAdTime = Date.now();
|
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
if (streamInfo == null) {
|
if (streamInfo == null) {
|
||||||
console.log('Unknown stream url ' + url);
|
console.log('Unknown stream url ' + url);
|
||||||
@ -364,33 +386,12 @@ twitch-videoad.js application/javascript
|
|||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
var newLines = [];
|
var newLines = [];
|
||||||
if (backupM3u8 != null) {
|
if (backupM3u8 != null) {
|
||||||
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
|
|
||||||
if (seqMatch != null) {
|
|
||||||
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
|
|
||||||
if (streamInfo.RealSeqNumber > 0) {
|
|
||||||
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
|
|
||||||
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
|
|
||||||
if (streamInfo.FakeSeqNumber == 0) {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
|
|
||||||
}
|
|
||||||
if (oldBackupSeqNumber == -1) {
|
|
||||||
// First backup sequence, assume +1
|
|
||||||
streamInfo.FakeSeqNumber++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
||||||
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
||||||
newLines.push('#EXTM3U');
|
newLines.push('#EXTM3U');
|
||||||
newLines.push('#EXT-X-VERSION:3');
|
newLines.push('#EXT-X-VERSION:3');
|
||||||
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
||||||
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
||||||
// The following will could cause issues when we stop stripping segments
|
// The following will could cause issues when we stop stripping segments
|
||||||
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
||||||
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
||||||
@ -418,6 +419,12 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finalSegUrl = getFinalSegUrl(newLines);
|
||||||
|
if (finalSegUrl != streamInfo.FinalSegUrl) {
|
||||||
|
streamInfo.FinalSegUrl = finalSegUrl;
|
||||||
|
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
|
||||||
|
}
|
||||||
|
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
|
||||||
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
||||||
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
||||||
} else {
|
} else {
|
||||||
@ -529,6 +536,7 @@ twitch-videoad.js application/javascript
|
|||||||
streamInfo.RealSeqNumber = -1;
|
streamInfo.RealSeqNumber = -1;
|
||||||
streamInfo.BackupSeqNumber = -1;
|
streamInfo.BackupSeqNumber = -1;
|
||||||
streamInfo.FakeSeqNumber = 0;
|
streamInfo.FakeSeqNumber = 0;
|
||||||
|
streamInfo.FinalSegUrl = null;
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
@ -860,6 +868,7 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pollForAds() {
|
function pollForAds() {
|
||||||
|
//console.log('pollForAds ' + new Date(Date.now()));
|
||||||
//check ad by looking for text banner
|
//check ad by looking for text banner
|
||||||
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
||||||
var foundAd = false;
|
var foundAd = false;
|
||||||
@ -893,9 +902,22 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(pollForAds,100);
|
//setTimeout(pollForAds,100);
|
||||||
}
|
}
|
||||||
function reloadTwitchPlayer() {
|
function pollForAdsObserver() {
|
||||||
|
pollForAds();
|
||||||
|
var vids = document.getElementsByClassName('video-player');
|
||||||
|
for (var i = 0; i < vids.length; i++) {
|
||||||
|
var observer = new MutationObserver(pollForAds);
|
||||||
|
observer.observe(vids[i], {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reloadTwitchPlayer(isPausePlay) {
|
||||||
// Taken from ttv-tools / ffz
|
// Taken from ttv-tools / ffz
|
||||||
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
||||||
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
||||||
@ -936,6 +958,11 @@ twitch-videoad.js application/javascript
|
|||||||
if (player.paused) {
|
if (player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isPausePlay) {
|
||||||
|
player.pause();
|
||||||
|
player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
||||||
if (sink && sink.video && sink.video._ffz_compressor) {
|
if (sink && sink.video && sink.video._ffz_compressor) {
|
||||||
const video = sink.video;
|
const video = sink.video;
|
||||||
@ -963,11 +990,11 @@ twitch-videoad.js application/javascript
|
|||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
} else {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hookFetch();
|
hookFetch();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name TwitchAdSolutions
|
// @name TwitchAdSolutions (video-swap)
|
||||||
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
||||||
// @version 1.3
|
// @version 1.4
|
||||||
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap/video-swap.user.js
|
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap/video-swap.user.js
|
||||||
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap/video-swap.user.js
|
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap/video-swap.user.js
|
||||||
// @description Multiple solutions for blocking Twitch ads (video-swap)
|
// @description Multiple solutions for blocking Twitch ads (video-swap)
|
||||||
@ -60,8 +60,6 @@
|
|||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.CurrentChannelNameFromM3U8 = null;
|
scope.CurrentChannelNameFromM3U8 = null;
|
||||||
scope.LastAdUrl = null;
|
|
||||||
scope.LastAdTime = 0;
|
|
||||||
// Need this in both scopes. Window scope needs to update this to worker scope.
|
// Need this in both scopes. Window scope needs to update this to worker scope.
|
||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
}
|
}
|
||||||
@ -93,6 +91,7 @@
|
|||||||
${processM3U8.toString()}
|
${processM3U8.toString()}
|
||||||
${getSegmentInfos.toString()}
|
${getSegmentInfos.toString()}
|
||||||
${getSegmentInfosLines.toString()}
|
${getSegmentInfosLines.toString()}
|
||||||
|
${getFinalSegUrl.toString()}
|
||||||
${hookWorkerFetch.toString()}
|
${hookWorkerFetch.toString()}
|
||||||
${declareOptions.toString()}
|
${declareOptions.toString()}
|
||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
@ -132,6 +131,9 @@
|
|||||||
else if (e.data.key == 'UboReloadPlayer') {
|
else if (e.data.key == 'UboReloadPlayer') {
|
||||||
reloadTwitchPlayer();
|
reloadTwitchPlayer();
|
||||||
}
|
}
|
||||||
|
else if (e.data.key == 'UboPauseResumePlayer') {
|
||||||
|
reloadTwitchPlayer(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getAdDiv() {
|
function getAdDiv() {
|
||||||
var playerRootDiv = document.querySelector('.video-player');
|
var playerRootDiv = document.querySelector('.video-player');
|
||||||
@ -207,7 +209,7 @@
|
|||||||
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
|
||||||
segInfo.dateTimeLineIndex = i - 2;
|
segInfo.dateTimeLineIndex = i - 2;
|
||||||
segInfo.dateTimeLine = lines[i - 2];
|
segInfo.dateTimeLine = lines[i - 2];
|
||||||
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
|
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
|
||||||
}
|
}
|
||||||
result.segs.push(segInfo);
|
result.segs.push(segInfo);
|
||||||
}
|
}
|
||||||
@ -263,20 +265,42 @@
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getFinalSegUrl(lines) {
|
||||||
|
for (var i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i].startsWith("http")) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
||||||
var si = StreamInfosByUrl[url];
|
var si = StreamInfosByUrl[url];
|
||||||
if (si != null) {
|
if (si != null) {
|
||||||
si.BackupSeqNumber = -1;
|
|
||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
||||||
var oldRealSeq = si.RealSeqNumber;
|
var oldRealSeq = si.RealSeqNumber;
|
||||||
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
|
||||||
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
if (!haveAdTags && si.FakeSeqNumber > 0) {
|
||||||
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
|
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
|
||||||
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
|
||||||
|
si.FakeSeqNumber = 0;
|
||||||
|
si.BackupSeqNumber = -1;
|
||||||
|
postMessage({key:'UboPauseResumePlayer'});*/
|
||||||
|
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
|
||||||
|
var finalSegUrl = getFinalSegUrl(lines);
|
||||||
|
if (finalSegUrl != si.FinalSegUrl) {
|
||||||
|
si.FinalSegUrl = finalSegUrl;
|
||||||
|
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
|
||||||
|
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
if (jump <= 3) {
|
||||||
|
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
|
||||||
|
} else if (jump > 0) {
|
||||||
|
si.FakeSeqNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
|
||||||
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
|
||||||
}
|
}
|
||||||
@ -325,8 +349,6 @@
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (haveAdTags) {
|
if (haveAdTags) {
|
||||||
LastAdUrl = url;
|
|
||||||
LastAdTime = Date.now();
|
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
if (streamInfo == null) {
|
if (streamInfo == null) {
|
||||||
console.log('Unknown stream url ' + url);
|
console.log('Unknown stream url ' + url);
|
||||||
@ -375,33 +397,12 @@
|
|||||||
var lines = textStr.replace('\r', '').split('\n');
|
var lines = textStr.replace('\r', '').split('\n');
|
||||||
var newLines = [];
|
var newLines = [];
|
||||||
if (backupM3u8 != null) {
|
if (backupM3u8 != null) {
|
||||||
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
|
|
||||||
if (seqMatch != null) {
|
|
||||||
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
|
|
||||||
if (streamInfo.RealSeqNumber > 0) {
|
|
||||||
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
|
|
||||||
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
|
|
||||||
if (streamInfo.FakeSeqNumber == 0) {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
|
|
||||||
}
|
|
||||||
if (oldBackupSeqNumber == -1) {
|
|
||||||
// First backup sequence, assume +1
|
|
||||||
streamInfo.FakeSeqNumber++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
var backupLines = backupM3u8.replace('\r', '').split('\n');
|
||||||
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
|
||||||
newLines.push('#EXTM3U');
|
newLines.push('#EXTM3U');
|
||||||
newLines.push('#EXT-X-VERSION:3');
|
newLines.push('#EXT-X-VERSION:3');
|
||||||
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
|
||||||
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
|
||||||
// The following will could cause issues when we stop stripping segments
|
// The following will could cause issues when we stop stripping segments
|
||||||
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
|
||||||
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
|
||||||
@ -429,6 +430,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var finalSegUrl = getFinalSegUrl(newLines);
|
||||||
|
if (finalSegUrl != streamInfo.FinalSegUrl) {
|
||||||
|
streamInfo.FinalSegUrl = finalSegUrl;
|
||||||
|
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
|
||||||
|
}
|
||||||
|
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
|
||||||
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
|
||||||
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
|
||||||
} else {
|
} else {
|
||||||
@ -540,6 +547,7 @@
|
|||||||
streamInfo.RealSeqNumber = -1;
|
streamInfo.RealSeqNumber = -1;
|
||||||
streamInfo.BackupSeqNumber = -1;
|
streamInfo.BackupSeqNumber = -1;
|
||||||
streamInfo.FakeSeqNumber = 0;
|
streamInfo.FakeSeqNumber = 0;
|
||||||
|
streamInfo.FinalSegUrl = null;
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
@ -871,6 +879,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function pollForAds() {
|
function pollForAds() {
|
||||||
|
//console.log('pollForAds ' + new Date(Date.now()));
|
||||||
//check ad by looking for text banner
|
//check ad by looking for text banner
|
||||||
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
|
||||||
var foundAd = false;
|
var foundAd = false;
|
||||||
@ -904,9 +913,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(pollForAds,100);
|
//setTimeout(pollForAds,100);
|
||||||
}
|
}
|
||||||
function reloadTwitchPlayer() {
|
function pollForAdsObserver() {
|
||||||
|
pollForAds();
|
||||||
|
var vids = document.getElementsByClassName('video-player');
|
||||||
|
for (var i = 0; i < vids.length; i++) {
|
||||||
|
var observer = new MutationObserver(pollForAds);
|
||||||
|
observer.observe(vids[i], {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reloadTwitchPlayer(isPausePlay) {
|
||||||
// Taken from ttv-tools / ffz
|
// Taken from ttv-tools / ffz
|
||||||
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
|
||||||
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
|
||||||
@ -947,6 +969,11 @@
|
|||||||
if (player.paused) {
|
if (player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isPausePlay) {
|
||||||
|
player.pause();
|
||||||
|
player.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
|
||||||
if (sink && sink.video && sink.video._ffz_compressor) {
|
if (sink && sink.video && sink.video._ffz_compressor) {
|
||||||
const video = sink.video;
|
const video = sink.video;
|
||||||
@ -974,11 +1001,11 @@
|
|||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
} else {
|
} else {
|
||||||
pollForAds();
|
pollForAdsObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hookFetch();
|
hookFetch();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user