diff --git a/YoutubeScript.js b/YoutubeScript.js
index 1f83ec0a5111dddb052bef75b7e9de8f0f78ba26..089b8e0f9bb191e7dbe4a625fdc17bbe4d6e3127 100644
--- a/YoutubeScript.js
+++ b/YoutubeScript.js
@@ -99,7 +99,7 @@ const USER_AGENT_TVHTML5_EMBED = "Mozilla/5.0 (CrKey armv7l 1.5.16041) AppleWebK
 const USE_MOBILE_PAGES = true;
 const USE_ANDROID_FALLBACK = false;
 const USE_IOS_FALLBACK = true;
-const USE_IOS_VIDEOS_FALLBACK = false;
+const USE_IOS_VIDEOS_FALLBACK = true;
 
 const SORT_VIEWS_STRING = "Views";
 const SORT_RATING_STRING = "Rating";
@@ -354,10 +354,11 @@ source.getChannelTemplateByClaimMap = () => {
 source.isContentDetailsUrl = (url) => {
 	return REGEX_VIDEO_URL_DESKTOP.test(url) || REGEX_VIDEO_URL_SHARE.test(url) || REGEX_VIDEO_URL_SHARE_LIVE.test(url) || REGEX_VIDEO_URL_SHORT.test(url) || REGEX_VIDEO_URL_CLIP.test(url) || REGEX_VIDEO_URL_EMBED.test(url);
 };
+
 source.getContentDetails = (url, useAuth, simplify) => {
 	useAuth = !!_settings?.authDetails || !!useAuth;
 
-    url = convertIfOtherUrl(url);
+	url = convertIfOtherUrl(url);
 
 	const clientContext = getClientContext(false);
 
@@ -371,13 +372,25 @@ source.getContentDetails = (url, useAuth, simplify) => {
 	headersUsed["Accept-Language"] = "en-US";
 	headersUsed["Cookie"] = "PREF=hl=en&gl=US"
 
-	const batch = http.batch().GET(url, headersUsed, useLogin);
-		
-	if(videoId && _settings["youtubeDislikes"] && !simplify)
+	let batchCounter = 1;
+	const batch = http.batch()
+		.GET(url, headersUsed, useLogin);
+	
+	let batchYoutubeDislikesIndex = -1;
+	if(videoId && _settings["youtubeDislikes"] && !simplify) {
 		batch.GET(URL_YOUTUBE_DISLIKES + videoId, {}, false);
+		batchYoutubeDislikesIndex = batchCounter++;
+	}
+
+	let batchIOS = -1;
+	if(USE_IOS_VIDEOS_FALLBACK) {
+		requestIOSStreamingData(videoId, batch);
+		batchIOS = batchCounter++;
+	}
+
 	const resps = batch.execute();
 
-    throwIfCaptcha(resps[0]);
+	throwIfCaptcha(resps[0]);
 	if(!resps[0].isOk) {
 		throw new ScriptException("Failed to request page [" + resps[0].code + "]");
 	}
@@ -386,40 +399,30 @@ source.getContentDetails = (url, useAuth, simplify) => {
 	let initialData = getInitialData(html);
 	let initialPlayerData = getInitialPlayerData(html);
 	let clientConfig = getClientConfig(html);
+	
+	
+	if (initialPlayerData.playabilityStatus?.status == "LOGIN_REQUIRED") {
+		if(!!_settings?.allowLoginFallback && !useLogin) {
+			bridge.toast("Using login fallback to resolve:\n" + initialPlayerData?.playabilityStatus?.reason);
+			resps[0] = http.GET(url, headersUsed, true);
 
-	let retryAttemptCount = 0;
-	let isValid = false;
-	const attemptCountMax = 1;
-	if(!simplify)
-		while(!isValid && retryAttemptCount < attemptCountMax) {
-			const invalidExperiments = [51217102, 51217476];
-			var invalidExperimentIndexes = invalidExperiments.map(x=>clientConfig.FEXP_EXPERIMENTS.indexOf(x));
-			if(clientConfig.FEXP_EXPERIMENTS && invalidExperimentIndexes.filter(x=>x >= 0).length > 0) {
-				retryAttemptCount++;
-				log("DETECTED BLOCKING ATTEMPT [" + JSON.stringify(invalidExperimentIndexes) + "]");
-				log("EXPIDS: " + JSON.stringify(clientConfig.FEXP_EXPERIMENTS));
-				//bridge.toast("Detected Youtube blocking attempt, bypassing.. (" + retryAttemptCount + ")");
-				
-				resps[0] = http.GET(url, headersUsed, false);
-				if(!resps[0].isOk)
-					throw new ScriptException("Failed to request page [" + resps[0].code + "]");
-				throwIfCaptcha(resps[0]);
-						
-				html = resps[0].body;//requestPage(url);
-				initialData = getInitialData(html);
-				initialPlayerData = getInitialPlayerData(html);
-				clientConfig = getClientConfig(html);
-				continue;
-			}
+			html = resps[0].body;//requestPage(url);
+			initialData = getInitialData(html);
+			initialPlayerData = getInitialPlayerData(html);
+			clientConfig = getClientConfig(html);
 
-			if(retryAttemptCount > 0) {
-				log("RESOLVED EXPIDS: " + JSON.stringify(clientConfig.FEXP_EXPERIMENTS))
-			}
-			isValid = true;
+			if (initialPlayerData.playabilityStatus?.status == "LOGIN_REQUIRED")
+				throw new ScriptException("Login required\nReason: " + initialPlayerData?.playabilityStatus?.reason);
 		}
+		else
+			throw new ScriptException("Login required\nReason: " + initialPlayerData?.playabilityStatus?.reason);
+	}
+	const invalidExperiments = [51217102, 51217476];
+	var invalidExperimentIndexes = invalidExperiments.map(x=>clientConfig.FEXP_EXPERIMENTS.indexOf(x));
+	const isExperiment = clientConfig.FEXP_EXPERIMENTS && invalidExperimentIndexes.filter(x=>x >= 0).length > 0;
 
 
-		if(initialPlayerData?.playabilityStatus?.status == "UNPLAYABLE")
+	if(initialPlayerData?.playabilityStatus?.status == "UNPLAYABLE")
 		throw new UnavailableException("Video unplayable");
 	
 	const jsUrlMatch = html.match("PLAYER_JS_URL\"\\s?:\\s?\"(.*?)\"");
@@ -451,19 +454,6 @@ source.getContentDetails = (url, useAuth, simplify) => {
 		}
 	}
 	
-	if (initialPlayerData.playabilityStatus?.status == "LOGIN_REQUIRED") {
-		throw new ScriptException("Login required\nReason: " + initialPlayerData?.playabilityStatus?.reason);
-	}
-	
-	let forceiOSSources = false;
-	if(retryAttemptCount >= attemptCountMax) {
-		bridge.toast("Detected Youtube blocking attempt, fallback to iOS.. (" + retryAttemptCount + ")");
-		forceiOSSources = true;
-	}
-	else if(retryAttemptCount > 0)
-		bridge.toast("Detected Youtube blocking attempt, bypassed.. (" + retryAttemptCount + ")");
-
-		
 	if(IS_TESTING) {
 		console.log("Initial Data", initialData);
 		console.log("Initial Player Data", initialPlayerData);
@@ -480,17 +470,17 @@ source.getContentDetails = (url, useAuth, simplify) => {
 		url: url
 	}, jsUrl, useLogin);
 	if(videoDetails == null)
-	    throw new UnavailableException("No video found");
+		throw new UnavailableException("No video found");
 
 	if(!videoDetails.live && 
 		(videoDetails.video?.videoSources == null || videoDetails.video.videoSources.length == 0) &&
 		(!videoDetails.datetime || videoDetails.datetime < (((new Date()).getTime() / 1000) - 60 * 60))) {
-        if(isNewCipher) {
-            log("Unavailable video found with new cipher, clearing cipher");
-            clearCipher(jsUrl);
-        }
+		if(isNewCipher) {
+			log("Unavailable video found with new cipher, clearing cipher");
+			clearCipher(jsUrl);
+		}
 		throw new UnavailableException("No sources found");
-    }
+	}
 
 	//Substitute Dash manifest from Android
 	if(USE_ANDROID_FALLBACK && videoDetails.dash && videoDetails.dash.url) {
@@ -507,44 +497,60 @@ source.getContentDetails = (url, useAuth, simplify) => {
 	}
 	//Substitute HLS manifest from iOS
 	if(USE_IOS_FALLBACK && videoDetails.hls && videoDetails.hls.url && !simplify) {
-		const iosData = requestIOSStreamingData(videoDetails.id.value);
-		if(IS_TESTING)
-			console.log("IOS Streaming Data", iosData);
-		if(iosData?.streamingData?.hlsManifestUrl) {
-			log("Using iOS HLS substitute");
-			const existingUrl = videoDetails.hls.url;
-			videoDetails.hls.name = "HLS (IOS)";
-			videoDetails.hls.url = iosData.streamingData.hlsManifestUrl;
-			if(existingUrl == videoDetails.live?.url) {
-				videoDetails.live.name = "HLS (IOS)";
-				videoDetails.live.url = iosData.streamingData.hlsManifestUrl;
+		const iosDataResp = (batchIOS > 0) ?
+			resps[batchIOS] : 
+			requestIOSStreamingData(videoDetails.id.value);
+			
+		if(iosDataResp.isOk) {
+			const iosData = JSON.parse(iosDataResp.body);
+			if(IS_TESTING)
+				console.log("IOS Streaming Data", iosData);
+			if(iosData?.streamingData?.hlsManifestUrl) {
+				log("Using iOS HLS substitute");
+				const existingUrl = videoDetails.hls.url;
+				videoDetails.hls.name = "HLS (IOS)";
+				videoDetails.hls.url = iosData.streamingData.hlsManifestUrl;
+				if(existingUrl == videoDetails.live?.url) {
+					videoDetails.live.name = "HLS (IOS)";
+					videoDetails.live.url = iosData.streamingData.hlsManifestUrl;
+				}
 			}
 		}
-	}
-	else if((USE_IOS_VIDEOS_FALLBACK || forceiOSSources) && !simplify) {
-		const iosData = requestIOSStreamingData(videoDetails.id.value);
-		if(IS_TESTING)
-			console.log("IOS Streaming Data", iosData);
+		else
+			bridge.toast("Failed to get iOS stream data");
+	}
+	else if(USE_IOS_VIDEOS_FALLBACK && !simplify) {
+		bridge.toast("Using iOS sources fallback (" + (batchIOS > 0 ? "cached" : "lazily") + ")");
+		const iosDataResp = (batchIOS > 0) ?
+			resps[batchIOS] : 
+			requestIOSStreamingData(videoDetails.id.value);
+		if(iosDataResp.isOk) {
+			const iosData = JSON.parse(iosDataResp.body);
+			if(IS_TESTING)
+				console.log("IOS Streaming Data", iosData);
 
-		if(iosData?.streamingData?.adaptiveFormats) {
-			let newDescriptor = extractAdaptiveFormats_VideoDescriptor(iosData.streamingData.adaptiveFormats, jsUrl, creationData, "IOS ");
-			videoDetails.video = newDescriptor;
+			if(iosData?.streamingData?.adaptiveFormats) {
+				let newDescriptor = extractAdaptiveFormats_VideoDescriptor(iosData.streamingData.adaptiveFormats, jsUrl, creationData, "IOS ");
+				videoDetails.video = newDescriptor;
+			}
 		}
+		else
+			bridge.toast("Failed to get iOS stream data");
 	}
 
-	if(resps.length > 1) {
+	if(batchYoutubeDislikesIndex > 0) {
 		try {
-            const youtubeDislikeInfoResponse = resps[1]
-            if(youtubeDislikeInfoResponse.isOk) {
-                const youtubeDislikeInfo = JSON.parse(youtubeDislikeInfoResponse.body);
-                if(IS_TESTING)
-                    console.log("Youtube Dislike Info", youtubeDislikeInfo);
-                videoDetails.rating = new RatingLikesDislikes(videoDetails.rating.likes, youtubeDislikeInfo.dislikes);
-            }
-        }
-        catch(ex) {
-            console.log("Failed to fetch Youtube Dislikes", ex);
-        }
+			const youtubeDislikeInfoResponse = resps[batchYoutubeDislikesIndex]
+			if(youtubeDislikeInfoResponse.isOk) {
+				const youtubeDislikeInfo = JSON.parse(youtubeDislikeInfoResponse.body);
+				if(IS_TESTING)
+					console.log("Youtube Dislike Info", youtubeDislikeInfo);
+				videoDetails.rating = new RatingLikesDislikes(videoDetails.rating.likes, youtubeDislikeInfo.dislikes);
+			}
+		}
+		catch(ex) {
+			console.log("Failed to fetch Youtube Dislikes", ex);
+		}
 	}
 
 	const finalResult = videoDetails;
@@ -2293,7 +2299,8 @@ function requestClientConfig(useMobile = false, useAuth = false) {
 	if(!resp.isOk) throw new ScriptException("Failed to request context requestClientConfig");
 	return getClientConfig(resp.body);
 }
-function requestIOSStreamingData(videoId) {
+
+function requestIOSStreamingData(videoId, batch) {
 	const body = {
 		videoId: videoId,
 		cpn: "" + randomString(16),
@@ -2330,11 +2337,14 @@ function requestIOSStreamingData(videoId) {
 		"&t=" + token +
 		"&id=" + videoId
 
-	const resp = http.POST(url, JSON.stringify(body), headers, false);
-	if(resp.isOk)
-		return JSON.parse(resp.body);
-	else
+	if(batch) {
+		batch.POST(url, JSON.stringify(body), headers, false);
 		return null;
+	}
+	else {
+		const resp = http.POST(url, JSON.stringify(body), headers, false);
+		return resp;
+	}
 }
 function requestAndroidStreamingData(videoId) {
 	const body = {