Fix low res stream and add fallback for video-swap-new
This commit is contained in:
@@ -2,7 +2,7 @@ twitch-videoad.js text/javascript
|
|||||||
(function() {
|
(function() {
|
||||||
if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; }
|
if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; }
|
||||||
'use strict';
|
'use strict';
|
||||||
var ourTwitchAdSolutionsVersion = 10;// Used to prevent conflicts with outdated versions of the scripts
|
var ourTwitchAdSolutionsVersion = 11;// Used to prevent conflicts with outdated versions of the scripts
|
||||||
if (typeof unsafeWindow === 'undefined') {
|
if (typeof unsafeWindow === 'undefined') {
|
||||||
unsafeWindow = window;
|
unsafeWindow = window;
|
||||||
}
|
}
|
||||||
@@ -692,7 +692,7 @@ twitch-videoad.js text/javascript
|
|||||||
}
|
}
|
||||||
function getAccessToken(channelName, playerType) {
|
function getAccessToken(channelName, playerType) {
|
||||||
var body = null;
|
var body = null;
|
||||||
var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "ios", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "ios", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}';
|
var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "android", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "android", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}';
|
||||||
body = {
|
body = {
|
||||||
operationName: 'PlaybackAccessToken_Template',
|
operationName: 'PlaybackAccessToken_Template',
|
||||||
query: templateQuery,
|
query: templateQuery,
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name TwitchAdSolutions (vaft)
|
// @name TwitchAdSolutions (vaft)
|
||||||
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
||||||
// @version 25.0.0
|
// @version 26.0.0
|
||||||
// @description Multiple solutions for blocking Twitch ads (vaft)
|
// @description Multiple solutions for blocking Twitch ads (vaft)
|
||||||
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js
|
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js
|
||||||
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js
|
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
var ourTwitchAdSolutionsVersion = 10;// Used to prevent conflicts with outdated versions of the scripts
|
var ourTwitchAdSolutionsVersion = 11;// Used to prevent conflicts with outdated versions of the scripts
|
||||||
if (typeof unsafeWindow === 'undefined') {
|
if (typeof unsafeWindow === 'undefined') {
|
||||||
unsafeWindow = window;
|
unsafeWindow = window;
|
||||||
}
|
}
|
||||||
@@ -704,7 +704,7 @@
|
|||||||
}
|
}
|
||||||
function getAccessToken(channelName, playerType) {
|
function getAccessToken(channelName, playerType) {
|
||||||
var body = null;
|
var body = null;
|
||||||
var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "ios", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "ios", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}';
|
var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "android", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "android", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}';
|
||||||
body = {
|
body = {
|
||||||
operationName: 'PlaybackAccessToken_Template',
|
operationName: 'PlaybackAccessToken_Template',
|
||||||
query: templateQuery,
|
query: templateQuery,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
twitch-videoad.js text/javascript
|
twitch-videoad.js text/javascript
|
||||||
(function() {
|
(function() {
|
||||||
if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; }
|
if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; }
|
||||||
var ourTwitchAdSolutionsVersion = 9;// Used to prevent conflicts with outdated versions of the scripts
|
var ourTwitchAdSolutionsVersion = 11;// Used to prevent conflicts with outdated versions of the scripts
|
||||||
if (typeof unsafeWindow === 'undefined') {
|
if (typeof unsafeWindow === 'undefined') {
|
||||||
unsafeWindow = window;
|
unsafeWindow = window;
|
||||||
}
|
}
|
||||||
@@ -16,9 +16,9 @@ twitch-videoad.js text/javascript
|
|||||||
scope.OPT_MODE_STRIP_AD_SEGMENTS = true;
|
scope.OPT_MODE_STRIP_AD_SEGMENTS = true;
|
||||||
scope.OPT_MODE_NOTIFY_ADS_WATCHED = true;
|
scope.OPT_MODE_NOTIFY_ADS_WATCHED = true;
|
||||||
scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = false;
|
scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = false;
|
||||||
scope.OPT_BACKUP_PLAYER_TYPE = 'autoplay';
|
scope.OPT_PREROLL_BACKUP_PLAYER_TYPES = [ 'autoplay', 'embed' ];
|
||||||
scope.OPT_BACKUP_PLATFORM = 'ios';
|
scope.OPT_MIDROLL_BACKUP_PLAYER_TYPES = [ 'autoplay', 'picture-by-picture' ,'embed' ];
|
||||||
scope.OPT_REGULAR_PLAYER_TYPE = 'site';
|
scope.OPT_BACKUP_PLATFORM = 'android';
|
||||||
scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = null;
|
scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = null;
|
||||||
scope.OPT_SHOW_AD_BANNER = true;
|
scope.OPT_SHOW_AD_BANNER = true;
|
||||||
scope.AD_SIGNIFIER = 'stitched-ad';
|
scope.AD_SIGNIFIER = 'stitched-ad';
|
||||||
@@ -113,6 +113,7 @@ twitch-videoad.js text/javascript
|
|||||||
${makeGraphQlPacket.toString()}
|
${makeGraphQlPacket.toString()}
|
||||||
${tryNotifyAdsWatchedM3U8.toString()}
|
${tryNotifyAdsWatchedM3U8.toString()}
|
||||||
${parseAttributes.toString()}
|
${parseAttributes.toString()}
|
||||||
|
${setStreamInfoUrls.toString()}
|
||||||
${onFoundAd.toString()}
|
${onFoundAd.toString()}
|
||||||
${getWasmWorkerJs.toString()}
|
${getWasmWorkerJs.toString()}
|
||||||
var workerString = getWasmWorkerJs('${twitchBlobUrl.replaceAll("'", "%27")}');
|
var workerString = getWasmWorkerJs('${twitchBlobUrl.replaceAll("'", "%27")}');
|
||||||
@@ -222,14 +223,102 @@ twitch-videoad.js text/javascript
|
|||||||
req.send();
|
req.send();
|
||||||
return req.responseText;
|
return req.responseText;
|
||||||
}
|
}
|
||||||
function onFoundAd(streamInfo, textStr, reloadPlayer) {
|
function setStreamInfoUrls(streamInfo, encodingsM3u8) {
|
||||||
console.log('Found ads, switch to backup');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
streamInfo.UseBackupStream = true;
|
for (var j = 0; j < lines.length; j++) {
|
||||||
|
if (!lines[j].startsWith('#') && lines[j].includes('.m3u8')) {
|
||||||
|
StreamInfosByUrl[lines[j].trimEnd()] = streamInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function onFoundAd(streamInfo, textStr, reloadPlayer, realFetch, url) {
|
||||||
|
var result = textStr;
|
||||||
streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"');
|
streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"');
|
||||||
|
var playerTypes = streamInfo.IsMidroll ? OPT_MIDROLL_BACKUP_PLAYER_TYPES : OPT_PREROLL_BACKUP_PLAYER_TYPES;
|
||||||
|
if (streamInfo.BackupEncodingsStatus.size >= playerTypes.length) {
|
||||||
|
return textStr;
|
||||||
|
}
|
||||||
|
if (streamInfo.BackupEncodings && !streamInfo.BackupEncodings.includes(url)) {
|
||||||
|
// Disabled for now as this may cause some problems
|
||||||
|
/*console.log('Double request before it managed to switch to the backup?');
|
||||||
|
var streamM3u8Url = streamInfo.BackupEncodings.match(/^https:.*\.m3u8.*$/m)[0];
|
||||||
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
|
if (streamM3u8Response.status === 200) {
|
||||||
|
return await streamM3u8Response.text();
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
var backupPlayerTypeInfo = '';
|
||||||
|
for (var i = 0; i < playerTypes.length; i++) {
|
||||||
|
var playerType = playerTypes[i];
|
||||||
|
if (!streamInfo.BackupEncodingsStatus.has(playerType)) {
|
||||||
|
try {
|
||||||
|
var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, playerType, OPT_BACKUP_PLATFORM);
|
||||||
|
if (accessTokenResponse != null && accessTokenResponse.status === 200) {
|
||||||
|
var accessToken = await accessTokenResponse.json();
|
||||||
|
var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.UsherParams);
|
||||||
|
urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature);
|
||||||
|
urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value);
|
||||||
|
var encodingsM3u8Response = await realFetch(urlInfo.href);
|
||||||
|
if (encodingsM3u8Response != null && encodingsM3u8Response.status === 200) {
|
||||||
|
var encodingsM3u8 = await encodingsM3u8Response.text();
|
||||||
|
var streamM3u8Url = encodingsM3u8.match(/^https:.*\.m3u8.*$/m)[0];
|
||||||
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
|
if (streamM3u8Response.status === 200) {
|
||||||
|
var backTextStr = await streamM3u8Response.text();
|
||||||
|
if (!backTextStr.includes(AD_SIGNIFIER) || streamInfo.BackupEncodingsStatus.size >= playerTypes.length - 1) {
|
||||||
|
result = backTextStr;
|
||||||
|
backupPlayerTypeInfo = ' (' + playerType + ')';
|
||||||
|
streamInfo.BackupEncodingsStatus.set(playerType, 1);
|
||||||
|
if (playerType !== 'embed') {
|
||||||
|
// Low resolution streams will reduce the number of resolutions in the UI. To fix this we merge the highest low res into the main m3u8
|
||||||
|
// TODO: Do a better matching up of the resolutions rather than picking the highest low res for all
|
||||||
|
var lowResLines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
|
var lowResBestUrl = null;
|
||||||
|
for (var j = 0; j < lowResLines.length; j++) {
|
||||||
|
if (lowResLines[j].startsWith('#EXT-X-STREAM-INF')) {
|
||||||
|
var res = parseAttributes(lowResLines[j])['RESOLUTION'];
|
||||||
|
if (res && lowResLines[j + 1].endsWith('.m3u8')) {
|
||||||
|
// Assumes resolutions are correctly ordered
|
||||||
|
lowResBestUrl = lowResLines[j + 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lowResBestUrl != null && streamInfo.Encodings != null) {
|
||||||
|
var normalEncodingsM3u8 = streamInfo.Encodings;
|
||||||
|
var normalLines = normalEncodingsM3u8.replace('\r', '').split('\n');
|
||||||
|
for (var j = 0; j < normalLines.length - 1; j++) {
|
||||||
|
if (normalLines[j].startsWith('#EXT-X-STREAM-INF')) {
|
||||||
|
var res = parseAttributes(normalLines[j])['RESOLUTION'];
|
||||||
|
if (res) {
|
||||||
|
lowResBestUrl += ' ';// The stream doesn't load unless each url line is unique
|
||||||
|
normalLines[j + 1] = lowResBestUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encodingsM3u8 = normalLines.join('\r\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
streamInfo.BackupEncodings = encodingsM3u8;
|
||||||
|
setStreamInfoUrls(streamInfo, encodingsM3u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) { console.error(err); }
|
||||||
|
if (streamInfo.BackupEncodingsStatus.get(playerType) === 1) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
streamInfo.BackupEncodingsStatus.set(playerType, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('Found ads, switch to backup' + backupPlayerTypeInfo);
|
||||||
if (reloadPlayer) {
|
if (reloadPlayer) {
|
||||||
postMessage({key:'UboReloadPlayer'});
|
postMessage({key:'UboReloadPlayer'});
|
||||||
}
|
}
|
||||||
postMessage({key:'UboShowAdBanner',isMidroll:streamInfo.IsMidroll});
|
postMessage({key:'UboShowAdBanner',isMidroll:streamInfo.IsMidroll});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
@@ -242,53 +331,43 @@ twitch-videoad.js text/javascript
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (streamInfo.UseBackupStream) {
|
if (streamInfo.BackupEncodings) {
|
||||||
if (streamInfo.Encodings == null) {
|
var streamM3u8Url = streamInfo.Encodings.match(/^https:.*\.m3u8$/m)[0];
|
||||||
console.log('Found backup stream but not main stream?');
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
streamInfo.UseBackupStream = false;
|
if (streamM3u8Response.status == 200) {
|
||||||
postMessage({key:'UboReloadPlayer'});
|
var streamM3u8 = await streamM3u8Response.text();
|
||||||
return '';
|
if (streamM3u8 != null) {
|
||||||
} else {
|
if (!streamM3u8.includes(AD_SIGNIFIER)) {
|
||||||
var streamM3u8Url = streamInfo.Encodings.match(/^https:.*\.m3u8$/m)[0];
|
console.log('No more ads on main stream. Triggering player reload to go back to main stream...');
|
||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
streamInfo.BackupEncodings = null;
|
||||||
if (streamM3u8Response.status == 200) {
|
streamInfo.BackupEncodingsStatus.clear();
|
||||||
var streamM3u8 = await streamM3u8Response.text();
|
postMessage({key:'UboHideAdBanner'});
|
||||||
if (streamM3u8 != null) {
|
postMessage({key:'UboReloadPlayer'});
|
||||||
if (!streamM3u8.includes(AD_SIGNIFIER)) {
|
} else if (!streamM3u8.includes('"MIDROLL"') && !streamM3u8.includes('"midroll"')) {
|
||||||
console.log('No more ads on main stream. Triggering player reload to go back to main stream...');
|
var lines = streamM3u8.replace('\r', '').split('\n');
|
||||||
streamInfo.UseBackupStream = false;
|
for (var i = 0; i < lines.length; i++) {
|
||||||
postMessage({key:'UboHideAdBanner'});
|
var line = lines[i];
|
||||||
postMessage({key:'UboReloadPlayer'});
|
if (line.startsWith('#EXTINF') && lines.length > i + 1) {
|
||||||
} else if (!streamM3u8.includes('"MIDROLL"') && !streamM3u8.includes('"midroll"')) {
|
if (!line.includes(LIVE_SIGNIFIER) && !streamInfo.RequestedAds.has(lines[i + 1])) {
|
||||||
var lines = streamM3u8.replace('\r', '').split('\n');
|
// Only request one .ts file per .m3u8 request to avoid making too many requests
|
||||||
for (var i = 0; i < lines.length; i++) {
|
//console.log('Fetch ad .ts file');
|
||||||
var line = lines[i];
|
streamInfo.RequestedAds.add(lines[i + 1]);
|
||||||
if (line.startsWith('#EXTINF') && lines.length > i + 1) {
|
fetch(lines[i + 1]).then((response)=>{response.blob()});
|
||||||
if (!line.includes(LIVE_SIGNIFIER) && !streamInfo.RequestedAds.has(lines[i + 1])) {
|
break;
|
||||||
// Only request one .ts file per .m3u8 request to avoid making too many requests
|
|
||||||
//console.log('Fetch ad .ts file');
|
|
||||||
streamInfo.RequestedAds.add(lines[i + 1]);
|
|
||||||
fetch(lines[i + 1]).then((response)=>{response.blob()});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (streamInfo.BackupEncodings && haveAdTags) {
|
||||||
|
textStr = await onFoundAd(streamInfo, textStr, true, realFetch, url);
|
||||||
|
}
|
||||||
} else if (haveAdTags) {
|
} else if (haveAdTags) {
|
||||||
onFoundAd(streamInfo, textStr, true);
|
textStr = await onFoundAd(streamInfo, textStr, true, realFetch, url);
|
||||||
} else {
|
} else {
|
||||||
postMessage({key:'UboHideAdBanner'});
|
postMessage({key:'UboHideAdBanner'});
|
||||||
}
|
}
|
||||||
if (haveAdTags && streamInfo.BackupEncodings != null) {
|
|
||||||
var streamM3u8Url = streamInfo.BackupEncodings.match(/^https:.*\.m3u8.*$/m)[0];
|
|
||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
|
||||||
if (streamM3u8Response.status == 200) {
|
|
||||||
textStr = await streamM3u8Response.text();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
function hookWorkerFetch() {
|
function hookWorkerFetch() {
|
||||||
@@ -300,12 +379,16 @@ twitch-videoad.js text/javascript
|
|||||||
if (url.endsWith('m3u8')) {
|
if (url.endsWith('m3u8')) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
var processAfter = async function(response) {
|
var processAfter = async function(response) {
|
||||||
var str = await processM3U8(url, await response.text(), realFetch);
|
if (response.status === 200) {
|
||||||
resolve(new Response(str, {
|
var str = await processM3U8(url, await response.text(), realFetch);
|
||||||
status: response.status,
|
resolve(new Response(str, {
|
||||||
statusText: response.statusText,
|
status: response.status,
|
||||||
headers: response.headers
|
statusText: response.statusText,
|
||||||
}));
|
headers: response.headers
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
resolve(response);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var send = function() {
|
var send = function() {
|
||||||
return realFetch(url, options).then(function(response) {
|
return realFetch(url, options).then(function(response) {
|
||||||
@@ -336,88 +419,39 @@ twitch-videoad.js text/javascript
|
|||||||
// The cached encodings are dead (the stream probably restarted)
|
// The cached encodings are dead (the stream probably restarted)
|
||||||
streamInfo = null;
|
streamInfo = null;
|
||||||
}
|
}
|
||||||
if (streamInfo == null || streamInfo.Encodings == null || streamInfo.BackupEncodings == null) {
|
if (streamInfo == null || streamInfo.Encodings == null) {
|
||||||
StreamInfos[channelName] = streamInfo = {
|
StreamInfos[channelName] = streamInfo = {
|
||||||
RequestedAds: new Set(),
|
RequestedAds: new Set(),
|
||||||
Encodings: null,
|
Encodings: null,
|
||||||
BackupEncodings: null,
|
BackupEncodings: null,
|
||||||
|
BackupEncodingsStatus: new Map(),
|
||||||
IsMidroll: false,
|
IsMidroll: false,
|
||||||
UseBackupStream: false,
|
UseFallbackStream: false,
|
||||||
ChannelName: channelName
|
ChannelName: channelName,
|
||||||
|
UsherParams: (new URL(url)).search
|
||||||
};
|
};
|
||||||
for (var i = 0; i < 2; i++) {
|
var encodingsM3u8Response = await realFetch(url, options);
|
||||||
var encodingsUrl = url;
|
if (encodingsM3u8Response != null && encodingsM3u8Response.status === 200) {
|
||||||
if (i == 1) {
|
var encodingsM3u8 = await encodingsM3u8Response.text();
|
||||||
var accessTokenResponse = await getAccessToken(channelName, OPT_BACKUP_PLAYER_TYPE, OPT_BACKUP_PLATFORM);
|
streamInfo.Encodings = encodingsM3u8;
|
||||||
if (accessTokenResponse != null && accessTokenResponse.status === 200) {
|
setStreamInfoUrls(streamInfo, encodingsM3u8);
|
||||||
var accessToken = await accessTokenResponse.json();
|
var streamM3u8Url = encodingsM3u8.match(/^https:.*\.m3u8$/m)[0];
|
||||||
var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8' + (new URL(url)).search);
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature);
|
if (streamM3u8Response.status == 200) {
|
||||||
urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value);
|
var streamM3u8 = await streamM3u8Response.text();
|
||||||
encodingsUrl = urlInfo.href;
|
if (streamM3u8.includes(AD_SIGNIFIER)) {
|
||||||
} else {
|
await onFoundAd(streamInfo, streamM3u8, false, realFetch, streamM3u8Url);
|
||||||
resolve(accessTokenResponse);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var encodingsM3u8Response = await realFetch(encodingsUrl, options);
|
|
||||||
if (encodingsM3u8Response != null && encodingsM3u8Response.status === 200) {
|
|
||||||
var encodingsM3u8 = await encodingsM3u8Response.text();
|
|
||||||
if (i == 0) {
|
|
||||||
streamInfo.Encodings = encodingsM3u8;
|
|
||||||
var streamM3u8Url = encodingsM3u8.match(/^https:.*\.m3u8$/m)[0];
|
|
||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
|
||||||
if (streamM3u8Response.status == 200) {
|
|
||||||
var streamM3u8 = await streamM3u8Response.text();
|
|
||||||
if (streamM3u8.includes(AD_SIGNIFIER)) {
|
|
||||||
onFoundAd(streamInfo, streamM3u8, false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resolve(streamM3u8Response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var lowResLines = encodingsM3u8.replace('\r', '').split('\n');
|
|
||||||
var lowResBestUrl = null;
|
|
||||||
for (var j = 0; j < lowResLines.length; j++) {
|
|
||||||
if (lowResLines[j].startsWith('#EXT-X-STREAM-INF')) {
|
|
||||||
var res = parseAttributes(lowResLines[j])['RESOLUTION'];
|
|
||||||
if (res && lowResLines[j + 1].endsWith('.m3u8')) {
|
|
||||||
// Assumes resolutions are correctly ordered
|
|
||||||
lowResBestUrl = lowResLines[j + 1];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lowResBestUrl != null && streamInfo.Encodings != null) {
|
|
||||||
var normalEncodingsM3u8 = streamInfo.Encodings;
|
|
||||||
var normalLines = normalEncodingsM3u8.replace('\r', '').split('\n');
|
|
||||||
for (var j = 0; j < normalLines.length - 1; j++) {
|
|
||||||
if (normalLines[j].startsWith('#EXT-X-STREAM-INF')) {
|
|
||||||
var res = parseAttributes(normalLines[j])['RESOLUTION'];
|
|
||||||
if (res) {
|
|
||||||
lowResBestUrl += ' ';// The stream doesn't load unless each url line is unique
|
|
||||||
normalLines[j + 1] = lowResBestUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
encodingsM3u8 = normalLines.join('\r\n');
|
|
||||||
}
|
|
||||||
streamInfo.BackupEncodings = encodingsM3u8;
|
|
||||||
}
|
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
|
||||||
for (var j = 0; j < lines.length; j++) {
|
|
||||||
if (!lines[j].startsWith('#') && lines[j].includes('.m3u8')) {
|
|
||||||
StreamInfosByUrl[lines[j].trimEnd()] = streamInfo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resolve(encodingsM3u8Response);
|
resolve(streamM3u8Response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
resolve(encodingsM3u8Response);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (streamInfo.UseBackupStream) {
|
if (streamInfo.BackupEncodings) {
|
||||||
resolve(new Response(streamInfo.BackupEncodings));
|
resolve(new Response(streamInfo.BackupEncodings));
|
||||||
} else {
|
} else {
|
||||||
resolve(new Response(streamInfo.Encodings));
|
resolve(new Response(streamInfo.Encodings));
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name TwitchAdSolutions (video-swap-new)
|
// @name TwitchAdSolutions (video-swap-new)
|
||||||
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
||||||
// @version 1.42
|
// @version 1.43
|
||||||
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap-new/video-swap-new.user.js
|
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap-new/video-swap-new.user.js
|
||||||
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap-new/video-swap-new.user.js
|
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap-new/video-swap-new.user.js
|
||||||
// @description Multiple solutions for blocking Twitch ads (video-swap-new)
|
// @description Multiple solutions for blocking Twitch ads (video-swap-new)
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
var ourTwitchAdSolutionsVersion = 9;// Used to prevent conflicts with outdated versions of the scripts
|
var ourTwitchAdSolutionsVersion = 11;// Used to prevent conflicts with outdated versions of the scripts
|
||||||
if (typeof unsafeWindow === 'undefined') {
|
if (typeof unsafeWindow === 'undefined') {
|
||||||
unsafeWindow = window;
|
unsafeWindow = window;
|
||||||
}
|
}
|
||||||
@@ -29,9 +29,9 @@
|
|||||||
scope.OPT_MODE_STRIP_AD_SEGMENTS = true;
|
scope.OPT_MODE_STRIP_AD_SEGMENTS = true;
|
||||||
scope.OPT_MODE_NOTIFY_ADS_WATCHED = true;
|
scope.OPT_MODE_NOTIFY_ADS_WATCHED = true;
|
||||||
scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = false;
|
scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = false;
|
||||||
scope.OPT_BACKUP_PLAYER_TYPE = 'autoplay';
|
scope.OPT_PREROLL_BACKUP_PLAYER_TYPES = [ 'autoplay', 'embed' ];
|
||||||
scope.OPT_BACKUP_PLATFORM = 'ios';
|
scope.OPT_MIDROLL_BACKUP_PLAYER_TYPES = [ 'autoplay', 'picture-by-picture' ,'embed' ];
|
||||||
scope.OPT_REGULAR_PLAYER_TYPE = 'site';
|
scope.OPT_BACKUP_PLATFORM = 'android';
|
||||||
scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = null;
|
scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = null;
|
||||||
scope.OPT_SHOW_AD_BANNER = true;
|
scope.OPT_SHOW_AD_BANNER = true;
|
||||||
scope.AD_SIGNIFIER = 'stitched-ad';
|
scope.AD_SIGNIFIER = 'stitched-ad';
|
||||||
@@ -126,6 +126,7 @@
|
|||||||
${makeGraphQlPacket.toString()}
|
${makeGraphQlPacket.toString()}
|
||||||
${tryNotifyAdsWatchedM3U8.toString()}
|
${tryNotifyAdsWatchedM3U8.toString()}
|
||||||
${parseAttributes.toString()}
|
${parseAttributes.toString()}
|
||||||
|
${setStreamInfoUrls.toString()}
|
||||||
${onFoundAd.toString()}
|
${onFoundAd.toString()}
|
||||||
${getWasmWorkerJs.toString()}
|
${getWasmWorkerJs.toString()}
|
||||||
var workerString = getWasmWorkerJs('${twitchBlobUrl.replaceAll("'", "%27")}');
|
var workerString = getWasmWorkerJs('${twitchBlobUrl.replaceAll("'", "%27")}');
|
||||||
@@ -235,14 +236,102 @@
|
|||||||
req.send();
|
req.send();
|
||||||
return req.responseText;
|
return req.responseText;
|
||||||
}
|
}
|
||||||
function onFoundAd(streamInfo, textStr, reloadPlayer) {
|
function setStreamInfoUrls(streamInfo, encodingsM3u8) {
|
||||||
console.log('Found ads, switch to backup');
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
streamInfo.UseBackupStream = true;
|
for (var j = 0; j < lines.length; j++) {
|
||||||
|
if (!lines[j].startsWith('#') && lines[j].includes('.m3u8')) {
|
||||||
|
StreamInfosByUrl[lines[j].trimEnd()] = streamInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function onFoundAd(streamInfo, textStr, reloadPlayer, realFetch, url) {
|
||||||
|
var result = textStr;
|
||||||
streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"');
|
streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"');
|
||||||
|
var playerTypes = streamInfo.IsMidroll ? OPT_MIDROLL_BACKUP_PLAYER_TYPES : OPT_PREROLL_BACKUP_PLAYER_TYPES;
|
||||||
|
if (streamInfo.BackupEncodingsStatus.size >= playerTypes.length) {
|
||||||
|
return textStr;
|
||||||
|
}
|
||||||
|
if (streamInfo.BackupEncodings && !streamInfo.BackupEncodings.includes(url)) {
|
||||||
|
// Disabled for now as this may cause some problems
|
||||||
|
/*console.log('Double request before it managed to switch to the backup?');
|
||||||
|
var streamM3u8Url = streamInfo.BackupEncodings.match(/^https:.*\.m3u8.*$/m)[0];
|
||||||
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
|
if (streamM3u8Response.status === 200) {
|
||||||
|
return await streamM3u8Response.text();
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
var backupPlayerTypeInfo = '';
|
||||||
|
for (var i = 0; i < playerTypes.length; i++) {
|
||||||
|
var playerType = playerTypes[i];
|
||||||
|
if (!streamInfo.BackupEncodingsStatus.has(playerType)) {
|
||||||
|
try {
|
||||||
|
var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, playerType, OPT_BACKUP_PLATFORM);
|
||||||
|
if (accessTokenResponse != null && accessTokenResponse.status === 200) {
|
||||||
|
var accessToken = await accessTokenResponse.json();
|
||||||
|
var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.UsherParams);
|
||||||
|
urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature);
|
||||||
|
urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value);
|
||||||
|
var encodingsM3u8Response = await realFetch(urlInfo.href);
|
||||||
|
if (encodingsM3u8Response != null && encodingsM3u8Response.status === 200) {
|
||||||
|
var encodingsM3u8 = await encodingsM3u8Response.text();
|
||||||
|
var streamM3u8Url = encodingsM3u8.match(/^https:.*\.m3u8.*$/m)[0];
|
||||||
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
|
if (streamM3u8Response.status === 200) {
|
||||||
|
var backTextStr = await streamM3u8Response.text();
|
||||||
|
if (!backTextStr.includes(AD_SIGNIFIER) || streamInfo.BackupEncodingsStatus.size >= playerTypes.length - 1) {
|
||||||
|
result = backTextStr;
|
||||||
|
backupPlayerTypeInfo = ' (' + playerType + ')';
|
||||||
|
streamInfo.BackupEncodingsStatus.set(playerType, 1);
|
||||||
|
if (playerType !== 'embed') {
|
||||||
|
// Low resolution streams will reduce the number of resolutions in the UI. To fix this we merge the highest low res into the main m3u8
|
||||||
|
// TODO: Do a better matching up of the resolutions rather than picking the highest low res for all
|
||||||
|
var lowResLines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
|
var lowResBestUrl = null;
|
||||||
|
for (var j = 0; j < lowResLines.length; j++) {
|
||||||
|
if (lowResLines[j].startsWith('#EXT-X-STREAM-INF')) {
|
||||||
|
var res = parseAttributes(lowResLines[j])['RESOLUTION'];
|
||||||
|
if (res && lowResLines[j + 1].endsWith('.m3u8')) {
|
||||||
|
// Assumes resolutions are correctly ordered
|
||||||
|
lowResBestUrl = lowResLines[j + 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lowResBestUrl != null && streamInfo.Encodings != null) {
|
||||||
|
var normalEncodingsM3u8 = streamInfo.Encodings;
|
||||||
|
var normalLines = normalEncodingsM3u8.replace('\r', '').split('\n');
|
||||||
|
for (var j = 0; j < normalLines.length - 1; j++) {
|
||||||
|
if (normalLines[j].startsWith('#EXT-X-STREAM-INF')) {
|
||||||
|
var res = parseAttributes(normalLines[j])['RESOLUTION'];
|
||||||
|
if (res) {
|
||||||
|
lowResBestUrl += ' ';// The stream doesn't load unless each url line is unique
|
||||||
|
normalLines[j + 1] = lowResBestUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encodingsM3u8 = normalLines.join('\r\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
streamInfo.BackupEncodings = encodingsM3u8;
|
||||||
|
setStreamInfoUrls(streamInfo, encodingsM3u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) { console.error(err); }
|
||||||
|
if (streamInfo.BackupEncodingsStatus.get(playerType) === 1) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
streamInfo.BackupEncodingsStatus.set(playerType, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('Found ads, switch to backup' + backupPlayerTypeInfo);
|
||||||
if (reloadPlayer) {
|
if (reloadPlayer) {
|
||||||
postMessage({key:'UboReloadPlayer'});
|
postMessage({key:'UboReloadPlayer'});
|
||||||
}
|
}
|
||||||
postMessage({key:'UboShowAdBanner',isMidroll:streamInfo.IsMidroll});
|
postMessage({key:'UboShowAdBanner',isMidroll:streamInfo.IsMidroll});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
var streamInfo = StreamInfosByUrl[url];
|
var streamInfo = StreamInfosByUrl[url];
|
||||||
@@ -255,53 +344,43 @@
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
if (streamInfo.UseBackupStream) {
|
if (streamInfo.BackupEncodings) {
|
||||||
if (streamInfo.Encodings == null) {
|
var streamM3u8Url = streamInfo.Encodings.match(/^https:.*\.m3u8$/m)[0];
|
||||||
console.log('Found backup stream but not main stream?');
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
streamInfo.UseBackupStream = false;
|
if (streamM3u8Response.status == 200) {
|
||||||
postMessage({key:'UboReloadPlayer'});
|
var streamM3u8 = await streamM3u8Response.text();
|
||||||
return '';
|
if (streamM3u8 != null) {
|
||||||
} else {
|
if (!streamM3u8.includes(AD_SIGNIFIER)) {
|
||||||
var streamM3u8Url = streamInfo.Encodings.match(/^https:.*\.m3u8$/m)[0];
|
console.log('No more ads on main stream. Triggering player reload to go back to main stream...');
|
||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
streamInfo.BackupEncodings = null;
|
||||||
if (streamM3u8Response.status == 200) {
|
streamInfo.BackupEncodingsStatus.clear();
|
||||||
var streamM3u8 = await streamM3u8Response.text();
|
postMessage({key:'UboHideAdBanner'});
|
||||||
if (streamM3u8 != null) {
|
postMessage({key:'UboReloadPlayer'});
|
||||||
if (!streamM3u8.includes(AD_SIGNIFIER)) {
|
} else if (!streamM3u8.includes('"MIDROLL"') && !streamM3u8.includes('"midroll"')) {
|
||||||
console.log('No more ads on main stream. Triggering player reload to go back to main stream...');
|
var lines = streamM3u8.replace('\r', '').split('\n');
|
||||||
streamInfo.UseBackupStream = false;
|
for (var i = 0; i < lines.length; i++) {
|
||||||
postMessage({key:'UboHideAdBanner'});
|
var line = lines[i];
|
||||||
postMessage({key:'UboReloadPlayer'});
|
if (line.startsWith('#EXTINF') && lines.length > i + 1) {
|
||||||
} else if (!streamM3u8.includes('"MIDROLL"') && !streamM3u8.includes('"midroll"')) {
|
if (!line.includes(LIVE_SIGNIFIER) && !streamInfo.RequestedAds.has(lines[i + 1])) {
|
||||||
var lines = streamM3u8.replace('\r', '').split('\n');
|
// Only request one .ts file per .m3u8 request to avoid making too many requests
|
||||||
for (var i = 0; i < lines.length; i++) {
|
//console.log('Fetch ad .ts file');
|
||||||
var line = lines[i];
|
streamInfo.RequestedAds.add(lines[i + 1]);
|
||||||
if (line.startsWith('#EXTINF') && lines.length > i + 1) {
|
fetch(lines[i + 1]).then((response)=>{response.blob()});
|
||||||
if (!line.includes(LIVE_SIGNIFIER) && !streamInfo.RequestedAds.has(lines[i + 1])) {
|
break;
|
||||||
// Only request one .ts file per .m3u8 request to avoid making too many requests
|
|
||||||
//console.log('Fetch ad .ts file');
|
|
||||||
streamInfo.RequestedAds.add(lines[i + 1]);
|
|
||||||
fetch(lines[i + 1]).then((response)=>{response.blob()});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (streamInfo.BackupEncodings && haveAdTags) {
|
||||||
|
textStr = await onFoundAd(streamInfo, textStr, true, realFetch, url);
|
||||||
|
}
|
||||||
} else if (haveAdTags) {
|
} else if (haveAdTags) {
|
||||||
onFoundAd(streamInfo, textStr, true);
|
textStr = await onFoundAd(streamInfo, textStr, true, realFetch, url);
|
||||||
} else {
|
} else {
|
||||||
postMessage({key:'UboHideAdBanner'});
|
postMessage({key:'UboHideAdBanner'});
|
||||||
}
|
}
|
||||||
if (haveAdTags && streamInfo.BackupEncodings != null) {
|
|
||||||
var streamM3u8Url = streamInfo.BackupEncodings.match(/^https:.*\.m3u8.*$/m)[0];
|
|
||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
|
||||||
if (streamM3u8Response.status == 200) {
|
|
||||||
textStr = await streamM3u8Response.text();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
function hookWorkerFetch() {
|
function hookWorkerFetch() {
|
||||||
@@ -313,12 +392,16 @@
|
|||||||
if (url.endsWith('m3u8')) {
|
if (url.endsWith('m3u8')) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
var processAfter = async function(response) {
|
var processAfter = async function(response) {
|
||||||
var str = await processM3U8(url, await response.text(), realFetch);
|
if (response.status === 200) {
|
||||||
resolve(new Response(str, {
|
var str = await processM3U8(url, await response.text(), realFetch);
|
||||||
status: response.status,
|
resolve(new Response(str, {
|
||||||
statusText: response.statusText,
|
status: response.status,
|
||||||
headers: response.headers
|
statusText: response.statusText,
|
||||||
}));
|
headers: response.headers
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
resolve(response);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var send = function() {
|
var send = function() {
|
||||||
return realFetch(url, options).then(function(response) {
|
return realFetch(url, options).then(function(response) {
|
||||||
@@ -349,88 +432,39 @@
|
|||||||
// The cached encodings are dead (the stream probably restarted)
|
// The cached encodings are dead (the stream probably restarted)
|
||||||
streamInfo = null;
|
streamInfo = null;
|
||||||
}
|
}
|
||||||
if (streamInfo == null || streamInfo.Encodings == null || streamInfo.BackupEncodings == null) {
|
if (streamInfo == null || streamInfo.Encodings == null) {
|
||||||
StreamInfos[channelName] = streamInfo = {
|
StreamInfos[channelName] = streamInfo = {
|
||||||
RequestedAds: new Set(),
|
RequestedAds: new Set(),
|
||||||
Encodings: null,
|
Encodings: null,
|
||||||
BackupEncodings: null,
|
BackupEncodings: null,
|
||||||
|
BackupEncodingsStatus: new Map(),
|
||||||
IsMidroll: false,
|
IsMidroll: false,
|
||||||
UseBackupStream: false,
|
UseFallbackStream: false,
|
||||||
ChannelName: channelName
|
ChannelName: channelName,
|
||||||
|
UsherParams: (new URL(url)).search
|
||||||
};
|
};
|
||||||
for (var i = 0; i < 2; i++) {
|
var encodingsM3u8Response = await realFetch(url, options);
|
||||||
var encodingsUrl = url;
|
if (encodingsM3u8Response != null && encodingsM3u8Response.status === 200) {
|
||||||
if (i == 1) {
|
var encodingsM3u8 = await encodingsM3u8Response.text();
|
||||||
var accessTokenResponse = await getAccessToken(channelName, OPT_BACKUP_PLAYER_TYPE, OPT_BACKUP_PLATFORM);
|
streamInfo.Encodings = encodingsM3u8;
|
||||||
if (accessTokenResponse != null && accessTokenResponse.status === 200) {
|
setStreamInfoUrls(streamInfo, encodingsM3u8);
|
||||||
var accessToken = await accessTokenResponse.json();
|
var streamM3u8Url = encodingsM3u8.match(/^https:.*\.m3u8$/m)[0];
|
||||||
var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8' + (new URL(url)).search);
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature);
|
if (streamM3u8Response.status == 200) {
|
||||||
urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value);
|
var streamM3u8 = await streamM3u8Response.text();
|
||||||
encodingsUrl = urlInfo.href;
|
if (streamM3u8.includes(AD_SIGNIFIER)) {
|
||||||
} else {
|
await onFoundAd(streamInfo, streamM3u8, false, realFetch, streamM3u8Url);
|
||||||
resolve(accessTokenResponse);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var encodingsM3u8Response = await realFetch(encodingsUrl, options);
|
|
||||||
if (encodingsM3u8Response != null && encodingsM3u8Response.status === 200) {
|
|
||||||
var encodingsM3u8 = await encodingsM3u8Response.text();
|
|
||||||
if (i == 0) {
|
|
||||||
streamInfo.Encodings = encodingsM3u8;
|
|
||||||
var streamM3u8Url = encodingsM3u8.match(/^https:.*\.m3u8$/m)[0];
|
|
||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
|
||||||
if (streamM3u8Response.status == 200) {
|
|
||||||
var streamM3u8 = await streamM3u8Response.text();
|
|
||||||
if (streamM3u8.includes(AD_SIGNIFIER)) {
|
|
||||||
onFoundAd(streamInfo, streamM3u8, false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resolve(streamM3u8Response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var lowResLines = encodingsM3u8.replace('\r', '').split('\n');
|
|
||||||
var lowResBestUrl = null;
|
|
||||||
for (var j = 0; j < lowResLines.length; j++) {
|
|
||||||
if (lowResLines[j].startsWith('#EXT-X-STREAM-INF')) {
|
|
||||||
var res = parseAttributes(lowResLines[j])['RESOLUTION'];
|
|
||||||
if (res && lowResLines[j + 1].endsWith('.m3u8')) {
|
|
||||||
// Assumes resolutions are correctly ordered
|
|
||||||
lowResBestUrl = lowResLines[j + 1];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lowResBestUrl != null && streamInfo.Encodings != null) {
|
|
||||||
var normalEncodingsM3u8 = streamInfo.Encodings;
|
|
||||||
var normalLines = normalEncodingsM3u8.replace('\r', '').split('\n');
|
|
||||||
for (var j = 0; j < normalLines.length - 1; j++) {
|
|
||||||
if (normalLines[j].startsWith('#EXT-X-STREAM-INF')) {
|
|
||||||
var res = parseAttributes(normalLines[j])['RESOLUTION'];
|
|
||||||
if (res) {
|
|
||||||
lowResBestUrl += ' ';// The stream doesn't load unless each url line is unique
|
|
||||||
normalLines[j + 1] = lowResBestUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
encodingsM3u8 = normalLines.join('\r\n');
|
|
||||||
}
|
|
||||||
streamInfo.BackupEncodings = encodingsM3u8;
|
|
||||||
}
|
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
|
||||||
for (var j = 0; j < lines.length; j++) {
|
|
||||||
if (!lines[j].startsWith('#') && lines[j].includes('.m3u8')) {
|
|
||||||
StreamInfosByUrl[lines[j].trimEnd()] = streamInfo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resolve(encodingsM3u8Response);
|
resolve(streamM3u8Response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
resolve(encodingsM3u8Response);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (streamInfo.UseBackupStream) {
|
if (streamInfo.BackupEncodings) {
|
||||||
resolve(new Response(streamInfo.BackupEncodings));
|
resolve(new Response(streamInfo.BackupEncodings));
|
||||||
} else {
|
} else {
|
||||||
resolve(new Response(streamInfo.Encodings));
|
resolve(new Response(streamInfo.Encodings));
|
||||||
|
|||||||
Reference in New Issue
Block a user