From 9d844bbf3ac6be327325b13a91a6b33f73c49c1d Mon Sep 17 00:00:00 2001 From: RblSb Date: Sun, 28 Apr 2024 07:23:25 +0300 Subject: Raw youtube fallback for unavailable videos Also: - fix `tryLocalIp` replacement (NAT workaround) - improve proxy headers a bit - use json2object fork for better generated diffs --- src/client/players/Youtube.hx | 69 +++++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 22 deletions(-) (limited to 'src/client/players') diff --git a/src/client/players/Youtube.hx b/src/client/players/Youtube.hx index a728012..5308b63 100644 --- a/src/client/players/Youtube.hx +++ b/src/client/players/Youtube.hx @@ -10,15 +10,11 @@ import js.Browser.document; import js.html.Element; import js.youtube.Youtube as YtInit; import js.youtube.YoutubePlayer; +import utils.YoutubeUtils; using StringTools; class Youtube implements IPlayer { - final matchId = ~/youtube\.com.*v=([A-z0-9_-]+)/; - final matchShort = ~/youtu\.be\/([A-z0-9_-]+)/; - final matchShorts = ~/youtube\.com\/shorts\/([A-z0-9_-]+)/; - final matchEmbed = ~/youtube\.com\/embed\/([A-z0-9_-]+)/; - final matchPlaylist = ~/youtube\.com.*list=([A-z0-9_-]+)/; final videosUrl = "https://www.googleapis.com/youtube/v3/videos"; final playlistUrl = "https://www.googleapis.com/youtube/v3/playlistItems"; final urlTitleDuration = "?part=snippet,contentDetails&fields=items(snippet/title,contentDetails/duration)"; @@ -41,25 +37,12 @@ class Youtube implements IPlayer { return extractVideoId(url) != "" || extractPlaylistId(url) != ""; } - public function extractVideoId(url:String):String { - if (matchId.match(url)) { - return matchId.matched(1); - } - if (matchShort.match(url)) { - return matchShort.matched(1); - } - if (matchShorts.match(url)) { - return matchShorts.matched(1); - } - if (matchEmbed.match(url)) { - return matchEmbed.matched(1); - } - return ""; + public function extractVideoId(url:String) { + return YoutubeUtils.extractVideoId(url); } - function extractPlaylistId(url:String):String { - if (!matchPlaylist.match(url)) return ""; - return matchPlaylist.matched(1); + public function extractPlaylistId(url:String) { + return YoutubeUtils.extractPlaylistId(url); } final matchHours = ~/([0-9]+)H/; @@ -256,11 +239,53 @@ class Youtube implements IPlayer { }, onPlaybackRateChange: e -> { player.onRateChange(); + }, + onError: e -> { + // TODO message error codes + trace('Error ${e.data}'); + rawSourceFallback(item.url); } } }); } + function rawSourceFallback(url:String):Void { + JsApi.once(GetYoutubeVideoInfo, event -> { + final data = event.getYoutubeVideoInfo; + final info = data.response; + final format = getBestStreamFormat(info) ?? { + trace("format not found in response info:"); + trace(info); + return; + }; + final item = player.getCurrentItem(); + item.url = format.url; + player.refresh(); + }); + main.send({ + type: GetYoutubeVideoInfo, + getYoutubeVideoInfo: { + url: url + } + }); + } + + function getBestStreamFormat(info:YouTubeVideoInfo):Null { + info.formats ??= []; + info.adaptiveFormats ??= []; + final formats = info.adaptiveFormats.concat(info.formats); + final qPriority = [1080, 720, 480, 360, 240]; + for (q in qPriority) { + final quality = '${q}p'; + for (format in formats) { + if (format.audioQuality == null) continue; // no sound + if (format.width == null) continue; // no video + if (format.qualityLabel == quality) return format; + } + } + return null; + } + public function removeVideo():Void { if (video == null) return; isLoaded = false; -- cgit v1.2.3