diff options
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/Main.hx | 12 | ||||
| -rw-r--r-- | src/client/Player.hx | 4 | ||||
| -rw-r--r-- | src/client/players/Youtube.hx | 69 |
3 files changed, 62 insertions, 23 deletions
diff --git a/src/client/Main.hx b/src/client/Main.hx index b1e0eb6..85db713 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -18,6 +18,7 @@ import js.html.Event; import js.html.InputElement; import js.html.KeyboardEvent; import js.html.MouseEvent; +import js.html.URL; import js.html.VideoElement; import js.html.WebSocket; @@ -387,7 +388,13 @@ class Main { public function tryLocalIp(url:String):String { if (host == globalIp) return url; - return url.replace(globalIp, host); + try { + final url = new URL(url); + url.hostname = url.hostname.replace(globalIp, host); + return '$url'; + } catch (e) { + return url; + } } function onMessage(e):Void { @@ -557,6 +564,9 @@ class Main { case Dump: Utils.saveFile("dump.json", ApplicationJson, data.dump.data); + + case GetYoutubeVideoInfo: + // handled by event listeners like `JsApi.once` } } diff --git a/src/client/Player.hx b/src/client/Player.hx index 75b44b5..06a8f3e 100644 --- a/src/client/Player.hx +++ b/src/client/Player.hx @@ -89,6 +89,10 @@ class Player { setItemElementType(el, videoList.getItem(pos).isTemp); } + public function getCurrentItem():VideoItem { + return videoList.currentItem; + } + function setPlayer(newPlayer:IPlayer):Void { if (player != newPlayer) { if (player != null) { 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<YoutubeVideoFormat> { + 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; |
