video-swap-new fix embed player pausing (#432) / video latency stat (#119) / fallback stream improvements (#450)
This commit is contained in:
@@ -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 = 11;// Used to prevent conflicts with outdated versions of the scripts
|
var ourTwitchAdSolutionsVersion = 12;// Used to prevent conflicts with outdated versions of the scripts
|
||||||
if (typeof unsafeWindow === 'undefined') {
|
if (typeof unsafeWindow === 'undefined') {
|
||||||
unsafeWindow = window;
|
unsafeWindow = window;
|
||||||
}
|
}
|
||||||
@@ -13,13 +13,11 @@ twitch-videoad.js text/javascript
|
|||||||
unsafeWindow.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
|
unsafeWindow.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
|
||||||
function declareOptions(scope) {
|
function declareOptions(scope) {
|
||||||
// Options / globals
|
// Options / globals
|
||||||
scope.OPT_MODE_STRIP_AD_SEGMENTS = true;
|
scope.OPT_PREROLL_BACKUP_PLAYER_TYPES = [ 'autoplay', 'picture-by-picture', 'embed' ];
|
||||||
scope.OPT_MODE_NOTIFY_ADS_WATCHED = true;
|
|
||||||
scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = false;
|
|
||||||
scope.OPT_PREROLL_BACKUP_PLAYER_TYPES = [ 'autoplay', 'embed' ];
|
|
||||||
scope.OPT_MIDROLL_BACKUP_PLAYER_TYPES = [ 'autoplay', 'picture-by-picture', 'embed' ];
|
scope.OPT_MIDROLL_BACKUP_PLAYER_TYPES = [ 'autoplay', 'picture-by-picture', 'embed' ];
|
||||||
scope.OPT_BACKUP_PLATFORM = 'android';
|
scope.OPT_BACKUP_PLATFORM = 'android';
|
||||||
scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = null;
|
scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = 'site';
|
||||||
|
scope.OPT_STRIP_PARENT_DOMAINS = true;
|
||||||
scope.OPT_SHOW_AD_BANNER = true;
|
scope.OPT_SHOW_AD_BANNER = true;
|
||||||
scope.AD_SIGNIFIER = 'stitched-ad';
|
scope.AD_SIGNIFIER = 'stitched-ad';
|
||||||
scope.LIVE_SIGNIFIER = ',live';
|
scope.LIVE_SIGNIFIER = ',live';
|
||||||
@@ -32,6 +30,7 @@ twitch-videoad.js text/javascript
|
|||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
scope.ClientIntegrityHeader = null;
|
scope.ClientIntegrityHeader = null;
|
||||||
scope.AuthorizationHeader = null;
|
scope.AuthorizationHeader = null;
|
||||||
|
scope.SimulatedAdsDepth = 0;
|
||||||
}
|
}
|
||||||
var twitchWorkers = [];
|
var twitchWorkers = [];
|
||||||
var workerStringConflicts = [
|
var workerStringConflicts = [
|
||||||
@@ -111,13 +110,17 @@ twitch-videoad.js text/javascript
|
|||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
${gqlRequest.toString()}
|
${gqlRequest.toString()}
|
||||||
${makeGraphQlPacket.toString()}
|
${makeGraphQlPacket.toString()}
|
||||||
${tryNotifyAdsWatchedM3U8.toString()}
|
|
||||||
${parseAttributes.toString()}
|
${parseAttributes.toString()}
|
||||||
${setStreamInfoUrls.toString()}
|
${setStreamInfoUrls.toString()}
|
||||||
${onFoundAd.toString()}
|
${onFoundAd.toString()}
|
||||||
${getWasmWorkerJs.toString()}
|
${getWasmWorkerJs.toString()}
|
||||||
|
${getServerTimeFromM3u8.toString()}
|
||||||
|
${replaceServerTimeInM3u8.toString()}
|
||||||
var workerString = getWasmWorkerJs('${twitchBlobUrl.replaceAll("'", "%27")}');
|
var workerString = getWasmWorkerJs('${twitchBlobUrl.replaceAll("'", "%27")}');
|
||||||
declareOptions(self);
|
declareOptions(self);
|
||||||
|
gql_device_id = ${gql_device_id ? "'" + gql_device_id + "'" : null};
|
||||||
|
AuthorizationHeader = ${AuthorizationHeader ? "'" + AuthorizationHeader + "'" : null};
|
||||||
|
ClientIntegrityHeader = ${AuthorizationHeader ? "'" + ClientIntegrityHeader + "'" : null};
|
||||||
self.addEventListener('message', function(e) {
|
self.addEventListener('message', function(e) {
|
||||||
if (e.data.key == 'UboUpdateDeviceId') {
|
if (e.data.key == 'UboUpdateDeviceId') {
|
||||||
gql_device_id = e.data.value;
|
gql_device_id = e.data.value;
|
||||||
@@ -142,6 +145,9 @@ twitch-videoad.js text/javascript
|
|||||||
resolve(response);
|
resolve(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (e.data.key == 'SimulateAds') {
|
||||||
|
SimulatedAdsDepth = e.data.value;
|
||||||
|
console.log('SimulatedAdsDepth:' + SimulatedAdsDepth);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
hookWorkerFetch();
|
hookWorkerFetch();
|
||||||
@@ -239,13 +245,12 @@ twitch-videoad.js text/javascript
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (streamInfo.BackupEncodings && !streamInfo.BackupEncodings.includes(url)) {
|
if (streamInfo.BackupEncodings && !streamInfo.BackupEncodings.includes(url)) {
|
||||||
// Disabled for now as this may cause some problems
|
// NOTE: This might cause some freezing issues
|
||||||
/*console.log('Double request before it managed to switch to the backup?');
|
|
||||||
var streamM3u8Url = streamInfo.BackupEncodings.match(/^https:.*\.m3u8.*$/m)[0];
|
var streamM3u8Url = streamInfo.BackupEncodings.match(/^https:.*\.m3u8.*$/m)[0];
|
||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
if (streamM3u8Response.status === 200) {
|
if (streamM3u8Response.status === 200) {
|
||||||
return await streamM3u8Response.text();
|
return await streamM3u8Response.text();
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
var backupPlayerTypeInfo = '';
|
var backupPlayerTypeInfo = '';
|
||||||
for (var i = 0; i < playerTypes.length; i++) {
|
for (var i = 0; i < playerTypes.length; i++) {
|
||||||
@@ -265,10 +270,11 @@ twitch-videoad.js text/javascript
|
|||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
if (streamM3u8Response.status === 200) {
|
if (streamM3u8Response.status === 200) {
|
||||||
var backTextStr = await streamM3u8Response.text();
|
var backTextStr = await streamM3u8Response.text();
|
||||||
if (!backTextStr.includes(AD_SIGNIFIER) || streamInfo.BackupEncodingsStatus.size >= playerTypes.length - 1) {
|
if ((!backTextStr.includes(AD_SIGNIFIER) && (SimulatedAdsDepth == 0 || i >= SimulatedAdsDepth - 1)) || streamInfo.BackupEncodingsStatus.size >= playerTypes.length - 1) {
|
||||||
result = backTextStr;
|
result = backTextStr;
|
||||||
backupPlayerTypeInfo = ' (' + playerType + ')';
|
backupPlayerTypeInfo = ' (' + playerType + ')';
|
||||||
streamInfo.BackupEncodingsStatus.set(playerType, 1);
|
streamInfo.BackupEncodingsStatus.set(playerType, 1);
|
||||||
|
streamInfo.BackupEncodingsPlayerTypeIndex = i;
|
||||||
if (playerType !== 'embed') {
|
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
|
// 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
|
// TODO: Do a better matching up of the resolutions rather than picking the highest low res for all
|
||||||
@@ -323,24 +329,26 @@ twitch-videoad.js text/javascript
|
|||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
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);
|
||||||
//postMessage({key:'UboHideAdBanner'});
|
//postMessage({key:'UboHideAdBanner'});
|
||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (!OPT_MODE_STRIP_AD_SEGMENTS) {
|
|
||||||
return textStr;
|
|
||||||
}
|
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
|
if (SimulatedAdsDepth && (!streamInfo.BackupEncodings || !streamInfo.BackupEncodings.includes(url) || SimulatedAdsDepth - 1 > streamInfo.BackupEncodingsPlayerTypeIndex)) {
|
||||||
|
haveAdTags = true;
|
||||||
|
}
|
||||||
if (streamInfo.BackupEncodings) {
|
if (streamInfo.BackupEncodings) {
|
||||||
var streamM3u8Url = streamInfo.Encodings.match(/^https:.*\.m3u8$/m)[0];
|
var streamM3u8Url = streamInfo.Encodings.match(/^https:.*\.m3u8$/m)[0];
|
||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
if (streamM3u8Response.status == 200) {
|
if (streamM3u8Response.status == 200) {
|
||||||
var streamM3u8 = await streamM3u8Response.text();
|
var streamM3u8 = await streamM3u8Response.text();
|
||||||
if (streamM3u8 != null) {
|
if (streamM3u8 != null) {
|
||||||
if (!streamM3u8.includes(AD_SIGNIFIER)) {
|
if (!streamM3u8.includes(AD_SIGNIFIER) && !SimulatedAdsDepth) {
|
||||||
console.log('No more ads on main stream. Triggering player reload to go back to main stream...');
|
console.log('No more ads on main stream. Triggering player reload to go back to main stream...');
|
||||||
|
streamInfo.IsMovingOffBackupEncodings = true;
|
||||||
streamInfo.BackupEncodings = null;
|
streamInfo.BackupEncodings = null;
|
||||||
streamInfo.BackupEncodingsStatus.clear();
|
streamInfo.BackupEncodingsStatus.clear();
|
||||||
|
streamInfo.BackupEncodingsPlayerTypeIndex = -1;
|
||||||
postMessage({key:'UboHideAdBanner'});
|
postMessage({key:'UboHideAdBanner'});
|
||||||
postMessage({key:'UboReloadPlayer'});
|
postMessage({key:'UboReloadPlayer'});
|
||||||
} else if (!streamM3u8.includes('"MIDROLL"') && !streamM3u8.includes('"midroll"')) {
|
} else if (!streamM3u8.includes('"MIDROLL"') && !streamM3u8.includes('"midroll"')) {
|
||||||
@@ -363,7 +371,7 @@ twitch-videoad.js text/javascript
|
|||||||
if (streamInfo.BackupEncodings && haveAdTags) {
|
if (streamInfo.BackupEncodings && haveAdTags) {
|
||||||
textStr = await onFoundAd(streamInfo, textStr, true, realFetch, url);
|
textStr = await onFoundAd(streamInfo, textStr, true, realFetch, url);
|
||||||
}
|
}
|
||||||
} else if (haveAdTags) {
|
} else if (haveAdTags && !streamInfo.IsMovingOffBackupEncodings) {
|
||||||
textStr = await onFoundAd(streamInfo, textStr, true, realFetch, url);
|
textStr = await onFoundAd(streamInfo, textStr, true, realFetch, url);
|
||||||
} else {
|
} else {
|
||||||
postMessage({key:'UboHideAdBanner'});
|
postMessage({key:'UboHideAdBanner'});
|
||||||
@@ -410,7 +418,12 @@ twitch-videoad.js text/javascript
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
CurrentChannelNameFromM3U8 = channelName;
|
CurrentChannelNameFromM3U8 = channelName;
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_STRIP_PARENT_DOMAINS) {
|
||||||
|
// parent_domains is used to determine if the player is embeded and stripping it gets rid of fake ads
|
||||||
|
var tempUrl = new URL(url);
|
||||||
|
tempUrl.searchParams.delete('parent_domains');
|
||||||
|
url = tempUrl.toString();
|
||||||
|
}
|
||||||
return new Promise(async function(resolve, reject) {
|
return new Promise(async function(resolve, reject) {
|
||||||
// - First m3u8 request is the m3u8 with the video encodings (360p,480p,720p,etc).
|
// - First m3u8 request is the m3u8 with the video encodings (360p,480p,720p,etc).
|
||||||
// - Second m3u8 request is the m3u8 for the given encoding obtained in the first request. At this point we will know if there's ads.
|
// - Second m3u8 request is the m3u8 for the given encoding obtained in the first request. At this point we will know if there's ads.
|
||||||
@@ -419,12 +432,15 @@ 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;
|
||||||
}
|
}
|
||||||
|
var serverTime = null;
|
||||||
if (streamInfo == null || streamInfo.Encodings == 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(),
|
BackupEncodingsStatus: new Map(),
|
||||||
|
BackupEncodingsPlayerTypeIndex: -1,
|
||||||
|
IsMovingOffBackupEncodings: false,
|
||||||
IsMidroll: false,
|
IsMidroll: false,
|
||||||
UseFallbackStream: false,
|
UseFallbackStream: false,
|
||||||
ChannelName: channelName,
|
ChannelName: channelName,
|
||||||
@@ -435,6 +451,7 @@ twitch-videoad.js text/javascript
|
|||||||
var encodingsM3u8 = await encodingsM3u8Response.text();
|
var encodingsM3u8 = await encodingsM3u8Response.text();
|
||||||
streamInfo.Encodings = encodingsM3u8;
|
streamInfo.Encodings = encodingsM3u8;
|
||||||
setStreamInfoUrls(streamInfo, encodingsM3u8);
|
setStreamInfoUrls(streamInfo, encodingsM3u8);
|
||||||
|
serverTime = getServerTimeFromM3u8(encodingsM3u8);
|
||||||
var streamM3u8Url = encodingsM3u8.match(/^https:.*\.m3u8$/m)[0];
|
var streamM3u8Url = encodingsM3u8.match(/^https:.*\.m3u8$/m)[0];
|
||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
if (streamM3u8Response.status == 200) {
|
if (streamM3u8Response.status == 200) {
|
||||||
@@ -451,18 +468,27 @@ twitch-videoad.js text/javascript
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (streamInfo.BackupEncodings) {
|
if (!serverTime) {
|
||||||
resolve(new Response(streamInfo.BackupEncodings));
|
var encodingsM3u8Response = await realFetch(url, options);
|
||||||
} else {
|
if (encodingsM3u8Response != null && encodingsM3u8Response.status === 200) {
|
||||||
resolve(new Response(streamInfo.Encodings));
|
serverTime = getServerTimeFromM3u8(await encodingsM3u8Response.text());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
streamInfo.IsMovingOffBackupEncodings = false;
|
||||||
|
resolve(new Response(replaceServerTimeInM3u8(streamInfo.BackupEncodings ? streamInfo.BackupEncodings : streamInfo.Encodings, serverTime)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return realFetch.apply(this, arguments);
|
return realFetch.apply(this, arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function getServerTimeFromM3u8(encodingsM3u8) {
|
||||||
|
var matches = encodingsM3u8.match('SERVER-TIME="([0-9.]+)"');
|
||||||
|
return matches.length > 1 ? matches[1] : null;
|
||||||
|
}
|
||||||
|
function replaceServerTimeInM3u8(encodingsM3u8, newServerTime) {
|
||||||
|
return newServerTime ? encodingsM3u8.replace(new RegExp('(SERVER-TIME=")[0-9.]+"'), `SERVER-TIME="${newServerTime}"`) : encodingsM3u8;
|
||||||
|
}
|
||||||
function makeGraphQlPacket(event, radToken, payload) {
|
function makeGraphQlPacket(event, radToken, payload) {
|
||||||
return [{
|
return [{
|
||||||
operationName: 'ClientSideAdEventHandling_RecordAdEvent',
|
operationName: 'ClientSideAdEventHandling_RecordAdEvent',
|
||||||
@@ -546,65 +572,6 @@ twitch-videoad.js text/javascript
|
|||||||
return [key, Number.isNaN(num) ? value.startsWith('"') ? JSON.parse(value) : value : num]
|
return [key, Number.isNaN(num) ? value.startsWith('"') ? JSON.parse(value) : value : num]
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
async function tryNotifyAdsWatchedM3U8(streamM3u8) {
|
|
||||||
try {
|
|
||||||
//console.log(streamM3u8);
|
|
||||||
if (!streamM3u8 || !streamM3u8.includes(AD_SIGNIFIER)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
var matches = streamM3u8.match(/#EXT-X-DATERANGE:(ID="stitched-ad-[^\n]+)\n/);
|
|
||||||
if (matches.length > 1) {
|
|
||||||
const attrString = matches[1];
|
|
||||||
const attr = parseAttributes(attrString);
|
|
||||||
var podLength = parseInt(attr['X-TV-TWITCH-AD-POD-LENGTH'] ? attr['X-TV-TWITCH-AD-POD-LENGTH'] : '1');
|
|
||||||
var podPosition = parseInt(attr['X-TV-TWITCH-AD-POD-POSITION'] ? attr['X-TV-TWITCH-AD-POD-POSITION'] : '0');
|
|
||||||
var radToken = attr['X-TV-TWITCH-AD-RADS-TOKEN'];
|
|
||||||
var lineItemId = attr['X-TV-TWITCH-AD-LINE-ITEM-ID'];
|
|
||||||
var orderId = attr['X-TV-TWITCH-AD-ORDER-ID'];
|
|
||||||
var creativeId = attr['X-TV-TWITCH-AD-CREATIVE-ID'];
|
|
||||||
var adId = attr['X-TV-TWITCH-AD-ADVERTISER-ID'];
|
|
||||||
var rollType = attr['X-TV-TWITCH-AD-ROLL-TYPE'].toLowerCase();
|
|
||||||
const baseData = {
|
|
||||||
stitched: true,
|
|
||||||
roll_type: rollType,
|
|
||||||
player_mute: false,
|
|
||||||
player_volume: 0.5,
|
|
||||||
visible: true,
|
|
||||||
};
|
|
||||||
for (let podPosition = 0; podPosition < podLength; podPosition++) {
|
|
||||||
if (OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS) {
|
|
||||||
// This is all that's actually required at the moment
|
|
||||||
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
|
|
||||||
} else {
|
|
||||||
const extendedData = {
|
|
||||||
...baseData,
|
|
||||||
ad_id: adId,
|
|
||||||
ad_position: podPosition,
|
|
||||||
duration: 30,
|
|
||||||
creative_id: creativeId,
|
|
||||||
total_ads: podLength,
|
|
||||||
order_id: orderId,
|
|
||||||
line_item_id: lineItemId,
|
|
||||||
};
|
|
||||||
await gqlRequest(makeGraphQlPacket('video_ad_impression', radToken, extendedData));
|
|
||||||
for (let quartile = 0; quartile < 4; quartile++) {
|
|
||||||
await gqlRequest(
|
|
||||||
makeGraphQlPacket('video_ad_quartile_complete', radToken, {
|
|
||||||
...extendedData,
|
|
||||||
quartile: quartile + 1,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function postTwitchWorkerMessage(key, value) {
|
function postTwitchWorkerMessage(key, value) {
|
||||||
twitchWorkers.forEach((worker) => {
|
twitchWorkers.forEach((worker) => {
|
||||||
worker.postMessage({key: key, value: value});
|
worker.postMessage({key: key, value: value});
|
||||||
@@ -700,30 +667,32 @@ twitch-videoad.js text/javascript
|
|||||||
if (gql_device_id) {
|
if (gql_device_id) {
|
||||||
postTwitchWorkerMessage('UboUpdateDeviceId', gql_device_id);
|
postTwitchWorkerMessage('UboUpdateDeviceId', gql_device_id);
|
||||||
}
|
}
|
||||||
if (typeof init.body === 'string' && init.body.includes('PlaybackAccessToken')) {
|
if (OPT_ACCESS_TOKEN_PLAYER_TYPE && typeof init.body === 'string' && init.body.includes('PlaybackAccessToken') && !init.body.includes('picture-by-picture')) {
|
||||||
if (OPT_ACCESS_TOKEN_PLAYER_TYPE) {
|
let replacedPlayerType = '';
|
||||||
const newBody = JSON.parse(init.body);
|
const newBody = JSON.parse(init.body);
|
||||||
if (Array.isArray(newBody)) {
|
if (Array.isArray(newBody)) {
|
||||||
for (let i = 0; i < newBody.length; i++) {
|
for (let i = 0; i < newBody.length; i++) {
|
||||||
|
if (newBody[i]?.variables?.playerType && newBody[i]?.variables?.playerType !== OPT_ACCESS_TOKEN_PLAYER_TYPE) {
|
||||||
|
replacedPlayerType = newBody[i].variables.playerType;
|
||||||
newBody[i].variables.playerType = OPT_ACCESS_TOKEN_PLAYER_TYPE;
|
newBody[i].variables.playerType = OPT_ACCESS_TOKEN_PLAYER_TYPE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (newBody?.variables?.playerType && newBody?.variables?.playerType !== OPT_ACCESS_TOKEN_PLAYER_TYPE) {
|
||||||
|
replacedPlayerType = newBody.variables.playerType;
|
||||||
newBody.variables.playerType = OPT_ACCESS_TOKEN_PLAYER_TYPE;
|
newBody.variables.playerType = OPT_ACCESS_TOKEN_PLAYER_TYPE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (replacedPlayerType) {
|
||||||
|
console.log(`Replaced '${replacedPlayerType}' player type with '${OPT_ACCESS_TOKEN_PLAYER_TYPE}' player type`);
|
||||||
init.body = JSON.stringify(newBody);
|
init.body = JSON.stringify(newBody);
|
||||||
}
|
}
|
||||||
if (typeof init.headers['Client-Integrity'] === 'string') {
|
|
||||||
ClientIntegrityHeader = init.headers['Client-Integrity'];
|
|
||||||
if (ClientIntegrityHeader) {
|
|
||||||
postTwitchWorkerMessage('UpdateClientIntegrityHeader', init.headers['Client-Integrity']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof init.headers['Authorization'] === 'string') {
|
|
||||||
AuthorizationHeader = init.headers['Authorization'];
|
|
||||||
if (AuthorizationHeader) {
|
|
||||||
postTwitchWorkerMessage('UpdateAuthorizationHeader', init.headers['Authorization']);
|
|
||||||
}
|
}
|
||||||
|
if (typeof init.headers['Client-Integrity'] === 'string' && init.headers['Client-Integrity'] !== ClientIntegrityHeader) {
|
||||||
|
postTwitchWorkerMessage('UpdateClientIntegrityHeader', ClientIntegrityHeader = init.headers['Client-Integrity']);
|
||||||
}
|
}
|
||||||
|
if (typeof init.headers['Authorization'] === 'string' && init.headers['Authorization'] !== AuthorizationHeader) {
|
||||||
|
postTwitchWorkerMessage('UpdateAuthorizationHeader', AuthorizationHeader = init.headers['Authorization']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -807,6 +776,7 @@ twitch-videoad.js text/javascript
|
|||||||
localStorage.setItem(lsKeyQuality, JSON.stringify({default:player.core.state.quality.group}));
|
localStorage.setItem(lsKeyQuality, JSON.stringify({default:player.core.state.quality.group}));
|
||||||
}
|
}
|
||||||
playerState.setSrc({ isNewMediaPlayerInstance: true, refreshAccessToken: true });
|
playerState.setSrc({ isNewMediaPlayerInstance: true, refreshAccessToken: true });
|
||||||
|
player.play();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
localStorage.setItem(lsKeyQuality, currentQualityLS);
|
localStorage.setItem(lsKeyQuality, currentQualityLS);
|
||||||
localStorage.setItem(lsKeyMuted, currentMutedLS);
|
localStorage.setItem(lsKeyMuted, currentMutedLS);
|
||||||
@@ -908,4 +878,11 @@ twitch-videoad.js text/javascript
|
|||||||
onContentLoaded();
|
onContentLoaded();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
unsafeWindow.simulateAds = (depth) => {
|
||||||
|
if (depth === undefined || depth < 0) {
|
||||||
|
console.log('Ad depth paramter required (0 = no simulated ad, 1+ = use backup player for given depth)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
postTwitchWorkerMessage('SimulateAds', depth);
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
@@ -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.43
|
// @version 1.44
|
||||||
// @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 = 11;// Used to prevent conflicts with outdated versions of the scripts
|
var ourTwitchAdSolutionsVersion = 12;// Used to prevent conflicts with outdated versions of the scripts
|
||||||
if (typeof unsafeWindow === 'undefined') {
|
if (typeof unsafeWindow === 'undefined') {
|
||||||
unsafeWindow = window;
|
unsafeWindow = window;
|
||||||
}
|
}
|
||||||
@@ -26,13 +26,11 @@
|
|||||||
unsafeWindow.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
|
unsafeWindow.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
|
||||||
function declareOptions(scope) {
|
function declareOptions(scope) {
|
||||||
// Options / globals
|
// Options / globals
|
||||||
scope.OPT_MODE_STRIP_AD_SEGMENTS = true;
|
scope.OPT_PREROLL_BACKUP_PLAYER_TYPES = [ 'autoplay', 'picture-by-picture', 'embed' ];
|
||||||
scope.OPT_MODE_NOTIFY_ADS_WATCHED = true;
|
|
||||||
scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = false;
|
|
||||||
scope.OPT_PREROLL_BACKUP_PLAYER_TYPES = [ 'autoplay', 'embed' ];
|
|
||||||
scope.OPT_MIDROLL_BACKUP_PLAYER_TYPES = [ 'autoplay', 'picture-by-picture', 'embed' ];
|
scope.OPT_MIDROLL_BACKUP_PLAYER_TYPES = [ 'autoplay', 'picture-by-picture', 'embed' ];
|
||||||
scope.OPT_BACKUP_PLATFORM = 'android';
|
scope.OPT_BACKUP_PLATFORM = 'android';
|
||||||
scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = null;
|
scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = 'site';
|
||||||
|
scope.OPT_STRIP_PARENT_DOMAINS = true;
|
||||||
scope.OPT_SHOW_AD_BANNER = true;
|
scope.OPT_SHOW_AD_BANNER = true;
|
||||||
scope.AD_SIGNIFIER = 'stitched-ad';
|
scope.AD_SIGNIFIER = 'stitched-ad';
|
||||||
scope.LIVE_SIGNIFIER = ',live';
|
scope.LIVE_SIGNIFIER = ',live';
|
||||||
@@ -45,6 +43,7 @@
|
|||||||
scope.gql_device_id = null;
|
scope.gql_device_id = null;
|
||||||
scope.ClientIntegrityHeader = null;
|
scope.ClientIntegrityHeader = null;
|
||||||
scope.AuthorizationHeader = null;
|
scope.AuthorizationHeader = null;
|
||||||
|
scope.SimulatedAdsDepth = 0;
|
||||||
}
|
}
|
||||||
var twitchWorkers = [];
|
var twitchWorkers = [];
|
||||||
var workerStringConflicts = [
|
var workerStringConflicts = [
|
||||||
@@ -124,13 +123,17 @@
|
|||||||
${getAccessToken.toString()}
|
${getAccessToken.toString()}
|
||||||
${gqlRequest.toString()}
|
${gqlRequest.toString()}
|
||||||
${makeGraphQlPacket.toString()}
|
${makeGraphQlPacket.toString()}
|
||||||
${tryNotifyAdsWatchedM3U8.toString()}
|
|
||||||
${parseAttributes.toString()}
|
${parseAttributes.toString()}
|
||||||
${setStreamInfoUrls.toString()}
|
${setStreamInfoUrls.toString()}
|
||||||
${onFoundAd.toString()}
|
${onFoundAd.toString()}
|
||||||
${getWasmWorkerJs.toString()}
|
${getWasmWorkerJs.toString()}
|
||||||
|
${getServerTimeFromM3u8.toString()}
|
||||||
|
${replaceServerTimeInM3u8.toString()}
|
||||||
var workerString = getWasmWorkerJs('${twitchBlobUrl.replaceAll("'", "%27")}');
|
var workerString = getWasmWorkerJs('${twitchBlobUrl.replaceAll("'", "%27")}');
|
||||||
declareOptions(self);
|
declareOptions(self);
|
||||||
|
gql_device_id = ${gql_device_id ? "'" + gql_device_id + "'" : null};
|
||||||
|
AuthorizationHeader = ${AuthorizationHeader ? "'" + AuthorizationHeader + "'" : null};
|
||||||
|
ClientIntegrityHeader = ${AuthorizationHeader ? "'" + ClientIntegrityHeader + "'" : null};
|
||||||
self.addEventListener('message', function(e) {
|
self.addEventListener('message', function(e) {
|
||||||
if (e.data.key == 'UboUpdateDeviceId') {
|
if (e.data.key == 'UboUpdateDeviceId') {
|
||||||
gql_device_id = e.data.value;
|
gql_device_id = e.data.value;
|
||||||
@@ -155,6 +158,9 @@
|
|||||||
resolve(response);
|
resolve(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (e.data.key == 'SimulateAds') {
|
||||||
|
SimulatedAdsDepth = e.data.value;
|
||||||
|
console.log('SimulatedAdsDepth:' + SimulatedAdsDepth);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
hookWorkerFetch();
|
hookWorkerFetch();
|
||||||
@@ -252,13 +258,12 @@
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (streamInfo.BackupEncodings && !streamInfo.BackupEncodings.includes(url)) {
|
if (streamInfo.BackupEncodings && !streamInfo.BackupEncodings.includes(url)) {
|
||||||
// Disabled for now as this may cause some problems
|
// NOTE: This might cause some freezing issues
|
||||||
/*console.log('Double request before it managed to switch to the backup?');
|
|
||||||
var streamM3u8Url = streamInfo.BackupEncodings.match(/^https:.*\.m3u8.*$/m)[0];
|
var streamM3u8Url = streamInfo.BackupEncodings.match(/^https:.*\.m3u8.*$/m)[0];
|
||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
if (streamM3u8Response.status === 200) {
|
if (streamM3u8Response.status === 200) {
|
||||||
return await streamM3u8Response.text();
|
return await streamM3u8Response.text();
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
var backupPlayerTypeInfo = '';
|
var backupPlayerTypeInfo = '';
|
||||||
for (var i = 0; i < playerTypes.length; i++) {
|
for (var i = 0; i < playerTypes.length; i++) {
|
||||||
@@ -278,10 +283,11 @@
|
|||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
if (streamM3u8Response.status === 200) {
|
if (streamM3u8Response.status === 200) {
|
||||||
var backTextStr = await streamM3u8Response.text();
|
var backTextStr = await streamM3u8Response.text();
|
||||||
if (!backTextStr.includes(AD_SIGNIFIER) || streamInfo.BackupEncodingsStatus.size >= playerTypes.length - 1) {
|
if ((!backTextStr.includes(AD_SIGNIFIER) && (SimulatedAdsDepth == 0 || i >= SimulatedAdsDepth - 1)) || streamInfo.BackupEncodingsStatus.size >= playerTypes.length - 1) {
|
||||||
result = backTextStr;
|
result = backTextStr;
|
||||||
backupPlayerTypeInfo = ' (' + playerType + ')';
|
backupPlayerTypeInfo = ' (' + playerType + ')';
|
||||||
streamInfo.BackupEncodingsStatus.set(playerType, 1);
|
streamInfo.BackupEncodingsStatus.set(playerType, 1);
|
||||||
|
streamInfo.BackupEncodingsPlayerTypeIndex = i;
|
||||||
if (playerType !== 'embed') {
|
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
|
// 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
|
// TODO: Do a better matching up of the resolutions rather than picking the highest low res for all
|
||||||
@@ -336,24 +342,26 @@
|
|||||||
async function processM3U8(url, textStr, realFetch) {
|
async function processM3U8(url, textStr, realFetch) {
|
||||||
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);
|
||||||
//postMessage({key:'UboHideAdBanner'});
|
//postMessage({key:'UboHideAdBanner'});
|
||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
if (!OPT_MODE_STRIP_AD_SEGMENTS) {
|
|
||||||
return textStr;
|
|
||||||
}
|
|
||||||
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
var haveAdTags = textStr.includes(AD_SIGNIFIER);
|
||||||
|
if (SimulatedAdsDepth && (!streamInfo.BackupEncodings || !streamInfo.BackupEncodings.includes(url) || SimulatedAdsDepth - 1 > streamInfo.BackupEncodingsPlayerTypeIndex)) {
|
||||||
|
haveAdTags = true;
|
||||||
|
}
|
||||||
if (streamInfo.BackupEncodings) {
|
if (streamInfo.BackupEncodings) {
|
||||||
var streamM3u8Url = streamInfo.Encodings.match(/^https:.*\.m3u8$/m)[0];
|
var streamM3u8Url = streamInfo.Encodings.match(/^https:.*\.m3u8$/m)[0];
|
||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
if (streamM3u8Response.status == 200) {
|
if (streamM3u8Response.status == 200) {
|
||||||
var streamM3u8 = await streamM3u8Response.text();
|
var streamM3u8 = await streamM3u8Response.text();
|
||||||
if (streamM3u8 != null) {
|
if (streamM3u8 != null) {
|
||||||
if (!streamM3u8.includes(AD_SIGNIFIER)) {
|
if (!streamM3u8.includes(AD_SIGNIFIER) && !SimulatedAdsDepth) {
|
||||||
console.log('No more ads on main stream. Triggering player reload to go back to main stream...');
|
console.log('No more ads on main stream. Triggering player reload to go back to main stream...');
|
||||||
|
streamInfo.IsMovingOffBackupEncodings = true;
|
||||||
streamInfo.BackupEncodings = null;
|
streamInfo.BackupEncodings = null;
|
||||||
streamInfo.BackupEncodingsStatus.clear();
|
streamInfo.BackupEncodingsStatus.clear();
|
||||||
|
streamInfo.BackupEncodingsPlayerTypeIndex = -1;
|
||||||
postMessage({key:'UboHideAdBanner'});
|
postMessage({key:'UboHideAdBanner'});
|
||||||
postMessage({key:'UboReloadPlayer'});
|
postMessage({key:'UboReloadPlayer'});
|
||||||
} else if (!streamM3u8.includes('"MIDROLL"') && !streamM3u8.includes('"midroll"')) {
|
} else if (!streamM3u8.includes('"MIDROLL"') && !streamM3u8.includes('"midroll"')) {
|
||||||
@@ -376,7 +384,7 @@
|
|||||||
if (streamInfo.BackupEncodings && haveAdTags) {
|
if (streamInfo.BackupEncodings && haveAdTags) {
|
||||||
textStr = await onFoundAd(streamInfo, textStr, true, realFetch, url);
|
textStr = await onFoundAd(streamInfo, textStr, true, realFetch, url);
|
||||||
}
|
}
|
||||||
} else if (haveAdTags) {
|
} else if (haveAdTags && !streamInfo.IsMovingOffBackupEncodings) {
|
||||||
textStr = await onFoundAd(streamInfo, textStr, true, realFetch, url);
|
textStr = await onFoundAd(streamInfo, textStr, true, realFetch, url);
|
||||||
} else {
|
} else {
|
||||||
postMessage({key:'UboHideAdBanner'});
|
postMessage({key:'UboHideAdBanner'});
|
||||||
@@ -423,7 +431,12 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
CurrentChannelNameFromM3U8 = channelName;
|
CurrentChannelNameFromM3U8 = channelName;
|
||||||
if (OPT_MODE_STRIP_AD_SEGMENTS) {
|
if (OPT_STRIP_PARENT_DOMAINS) {
|
||||||
|
// parent_domains is used to determine if the player is embeded and stripping it gets rid of fake ads
|
||||||
|
var tempUrl = new URL(url);
|
||||||
|
tempUrl.searchParams.delete('parent_domains');
|
||||||
|
url = tempUrl.toString();
|
||||||
|
}
|
||||||
return new Promise(async function(resolve, reject) {
|
return new Promise(async function(resolve, reject) {
|
||||||
// - First m3u8 request is the m3u8 with the video encodings (360p,480p,720p,etc).
|
// - First m3u8 request is the m3u8 with the video encodings (360p,480p,720p,etc).
|
||||||
// - Second m3u8 request is the m3u8 for the given encoding obtained in the first request. At this point we will know if there's ads.
|
// - Second m3u8 request is the m3u8 for the given encoding obtained in the first request. At this point we will know if there's ads.
|
||||||
@@ -432,12 +445,15 @@
|
|||||||
// The cached encodings are dead (the stream probably restarted)
|
// The cached encodings are dead (the stream probably restarted)
|
||||||
streamInfo = null;
|
streamInfo = null;
|
||||||
}
|
}
|
||||||
|
var serverTime = null;
|
||||||
if (streamInfo == null || streamInfo.Encodings == 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(),
|
BackupEncodingsStatus: new Map(),
|
||||||
|
BackupEncodingsPlayerTypeIndex: -1,
|
||||||
|
IsMovingOffBackupEncodings: false,
|
||||||
IsMidroll: false,
|
IsMidroll: false,
|
||||||
UseFallbackStream: false,
|
UseFallbackStream: false,
|
||||||
ChannelName: channelName,
|
ChannelName: channelName,
|
||||||
@@ -448,6 +464,7 @@
|
|||||||
var encodingsM3u8 = await encodingsM3u8Response.text();
|
var encodingsM3u8 = await encodingsM3u8Response.text();
|
||||||
streamInfo.Encodings = encodingsM3u8;
|
streamInfo.Encodings = encodingsM3u8;
|
||||||
setStreamInfoUrls(streamInfo, encodingsM3u8);
|
setStreamInfoUrls(streamInfo, encodingsM3u8);
|
||||||
|
serverTime = getServerTimeFromM3u8(encodingsM3u8);
|
||||||
var streamM3u8Url = encodingsM3u8.match(/^https:.*\.m3u8$/m)[0];
|
var streamM3u8Url = encodingsM3u8.match(/^https:.*\.m3u8$/m)[0];
|
||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
if (streamM3u8Response.status == 200) {
|
if (streamM3u8Response.status == 200) {
|
||||||
@@ -464,18 +481,27 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (streamInfo.BackupEncodings) {
|
if (!serverTime) {
|
||||||
resolve(new Response(streamInfo.BackupEncodings));
|
var encodingsM3u8Response = await realFetch(url, options);
|
||||||
} else {
|
if (encodingsM3u8Response != null && encodingsM3u8Response.status === 200) {
|
||||||
resolve(new Response(streamInfo.Encodings));
|
serverTime = getServerTimeFromM3u8(await encodingsM3u8Response.text());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
streamInfo.IsMovingOffBackupEncodings = false;
|
||||||
|
resolve(new Response(replaceServerTimeInM3u8(streamInfo.BackupEncodings ? streamInfo.BackupEncodings : streamInfo.Encodings, serverTime)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return realFetch.apply(this, arguments);
|
return realFetch.apply(this, arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function getServerTimeFromM3u8(encodingsM3u8) {
|
||||||
|
var matches = encodingsM3u8.match('SERVER-TIME="([0-9.]+)"');
|
||||||
|
return matches.length > 1 ? matches[1] : null;
|
||||||
|
}
|
||||||
|
function replaceServerTimeInM3u8(encodingsM3u8, newServerTime) {
|
||||||
|
return newServerTime ? encodingsM3u8.replace(new RegExp('(SERVER-TIME=")[0-9.]+"'), `SERVER-TIME="${newServerTime}"`) : encodingsM3u8;
|
||||||
|
}
|
||||||
function makeGraphQlPacket(event, radToken, payload) {
|
function makeGraphQlPacket(event, radToken, payload) {
|
||||||
return [{
|
return [{
|
||||||
operationName: 'ClientSideAdEventHandling_RecordAdEvent',
|
operationName: 'ClientSideAdEventHandling_RecordAdEvent',
|
||||||
@@ -559,65 +585,6 @@
|
|||||||
return [key, Number.isNaN(num) ? value.startsWith('"') ? JSON.parse(value) : value : num]
|
return [key, Number.isNaN(num) ? value.startsWith('"') ? JSON.parse(value) : value : num]
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
async function tryNotifyAdsWatchedM3U8(streamM3u8) {
|
|
||||||
try {
|
|
||||||
//console.log(streamM3u8);
|
|
||||||
if (!streamM3u8 || !streamM3u8.includes(AD_SIGNIFIER)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
var matches = streamM3u8.match(/#EXT-X-DATERANGE:(ID="stitched-ad-[^\n]+)\n/);
|
|
||||||
if (matches.length > 1) {
|
|
||||||
const attrString = matches[1];
|
|
||||||
const attr = parseAttributes(attrString);
|
|
||||||
var podLength = parseInt(attr['X-TV-TWITCH-AD-POD-LENGTH'] ? attr['X-TV-TWITCH-AD-POD-LENGTH'] : '1');
|
|
||||||
var podPosition = parseInt(attr['X-TV-TWITCH-AD-POD-POSITION'] ? attr['X-TV-TWITCH-AD-POD-POSITION'] : '0');
|
|
||||||
var radToken = attr['X-TV-TWITCH-AD-RADS-TOKEN'];
|
|
||||||
var lineItemId = attr['X-TV-TWITCH-AD-LINE-ITEM-ID'];
|
|
||||||
var orderId = attr['X-TV-TWITCH-AD-ORDER-ID'];
|
|
||||||
var creativeId = attr['X-TV-TWITCH-AD-CREATIVE-ID'];
|
|
||||||
var adId = attr['X-TV-TWITCH-AD-ADVERTISER-ID'];
|
|
||||||
var rollType = attr['X-TV-TWITCH-AD-ROLL-TYPE'].toLowerCase();
|
|
||||||
const baseData = {
|
|
||||||
stitched: true,
|
|
||||||
roll_type: rollType,
|
|
||||||
player_mute: false,
|
|
||||||
player_volume: 0.5,
|
|
||||||
visible: true,
|
|
||||||
};
|
|
||||||
for (let podPosition = 0; podPosition < podLength; podPosition++) {
|
|
||||||
if (OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS) {
|
|
||||||
// This is all that's actually required at the moment
|
|
||||||
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
|
|
||||||
} else {
|
|
||||||
const extendedData = {
|
|
||||||
...baseData,
|
|
||||||
ad_id: adId,
|
|
||||||
ad_position: podPosition,
|
|
||||||
duration: 30,
|
|
||||||
creative_id: creativeId,
|
|
||||||
total_ads: podLength,
|
|
||||||
order_id: orderId,
|
|
||||||
line_item_id: lineItemId,
|
|
||||||
};
|
|
||||||
await gqlRequest(makeGraphQlPacket('video_ad_impression', radToken, extendedData));
|
|
||||||
for (let quartile = 0; quartile < 4; quartile++) {
|
|
||||||
await gqlRequest(
|
|
||||||
makeGraphQlPacket('video_ad_quartile_complete', radToken, {
|
|
||||||
...extendedData,
|
|
||||||
quartile: quartile + 1,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function postTwitchWorkerMessage(key, value) {
|
function postTwitchWorkerMessage(key, value) {
|
||||||
twitchWorkers.forEach((worker) => {
|
twitchWorkers.forEach((worker) => {
|
||||||
worker.postMessage({key: key, value: value});
|
worker.postMessage({key: key, value: value});
|
||||||
@@ -713,30 +680,32 @@
|
|||||||
if (gql_device_id) {
|
if (gql_device_id) {
|
||||||
postTwitchWorkerMessage('UboUpdateDeviceId', gql_device_id);
|
postTwitchWorkerMessage('UboUpdateDeviceId', gql_device_id);
|
||||||
}
|
}
|
||||||
if (typeof init.body === 'string' && init.body.includes('PlaybackAccessToken')) {
|
if (OPT_ACCESS_TOKEN_PLAYER_TYPE && typeof init.body === 'string' && init.body.includes('PlaybackAccessToken') && !init.body.includes('picture-by-picture')) {
|
||||||
if (OPT_ACCESS_TOKEN_PLAYER_TYPE) {
|
let replacedPlayerType = '';
|
||||||
const newBody = JSON.parse(init.body);
|
const newBody = JSON.parse(init.body);
|
||||||
if (Array.isArray(newBody)) {
|
if (Array.isArray(newBody)) {
|
||||||
for (let i = 0; i < newBody.length; i++) {
|
for (let i = 0; i < newBody.length; i++) {
|
||||||
|
if (newBody[i]?.variables?.playerType && newBody[i]?.variables?.playerType !== OPT_ACCESS_TOKEN_PLAYER_TYPE) {
|
||||||
|
replacedPlayerType = newBody[i].variables.playerType;
|
||||||
newBody[i].variables.playerType = OPT_ACCESS_TOKEN_PLAYER_TYPE;
|
newBody[i].variables.playerType = OPT_ACCESS_TOKEN_PLAYER_TYPE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (newBody?.variables?.playerType && newBody?.variables?.playerType !== OPT_ACCESS_TOKEN_PLAYER_TYPE) {
|
||||||
|
replacedPlayerType = newBody.variables.playerType;
|
||||||
newBody.variables.playerType = OPT_ACCESS_TOKEN_PLAYER_TYPE;
|
newBody.variables.playerType = OPT_ACCESS_TOKEN_PLAYER_TYPE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (replacedPlayerType) {
|
||||||
|
console.log(`Replaced '${replacedPlayerType}' player type with '${OPT_ACCESS_TOKEN_PLAYER_TYPE}' player type`);
|
||||||
init.body = JSON.stringify(newBody);
|
init.body = JSON.stringify(newBody);
|
||||||
}
|
}
|
||||||
if (typeof init.headers['Client-Integrity'] === 'string') {
|
|
||||||
ClientIntegrityHeader = init.headers['Client-Integrity'];
|
|
||||||
if (ClientIntegrityHeader) {
|
|
||||||
postTwitchWorkerMessage('UpdateClientIntegrityHeader', init.headers['Client-Integrity']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof init.headers['Authorization'] === 'string') {
|
|
||||||
AuthorizationHeader = init.headers['Authorization'];
|
|
||||||
if (AuthorizationHeader) {
|
|
||||||
postTwitchWorkerMessage('UpdateAuthorizationHeader', init.headers['Authorization']);
|
|
||||||
}
|
}
|
||||||
|
if (typeof init.headers['Client-Integrity'] === 'string' && init.headers['Client-Integrity'] !== ClientIntegrityHeader) {
|
||||||
|
postTwitchWorkerMessage('UpdateClientIntegrityHeader', ClientIntegrityHeader = init.headers['Client-Integrity']);
|
||||||
}
|
}
|
||||||
|
if (typeof init.headers['Authorization'] === 'string' && init.headers['Authorization'] !== AuthorizationHeader) {
|
||||||
|
postTwitchWorkerMessage('UpdateAuthorizationHeader', AuthorizationHeader = init.headers['Authorization']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -820,6 +789,7 @@
|
|||||||
localStorage.setItem(lsKeyQuality, JSON.stringify({default:player.core.state.quality.group}));
|
localStorage.setItem(lsKeyQuality, JSON.stringify({default:player.core.state.quality.group}));
|
||||||
}
|
}
|
||||||
playerState.setSrc({ isNewMediaPlayerInstance: true, refreshAccessToken: true });
|
playerState.setSrc({ isNewMediaPlayerInstance: true, refreshAccessToken: true });
|
||||||
|
player.play();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
localStorage.setItem(lsKeyQuality, currentQualityLS);
|
localStorage.setItem(lsKeyQuality, currentQualityLS);
|
||||||
localStorage.setItem(lsKeyMuted, currentMutedLS);
|
localStorage.setItem(lsKeyMuted, currentMutedLS);
|
||||||
@@ -921,4 +891,11 @@
|
|||||||
onContentLoaded();
|
onContentLoaded();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
unsafeWindow.simulateAds = (depth) => {
|
||||||
|
if (depth === undefined || depth < 0) {
|
||||||
|
console.log('Ad depth paramter required (0 = no simulated ad, 1+ = use backup player for given depth)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
postTwitchWorkerMessage('SimulateAds', depth);
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
Reference in New Issue
Block a user