diff --git a/workshop/gamemodes/cinema_modded/gamemode/extensions/sh_util.lua b/workshop/gamemodes/cinema_modded/gamemode/extensions/sh_util.lua
index 9b19e42..97f8f70 100644
--- a/workshop/gamemodes/cinema_modded/gamemode/extensions/sh_util.lua
+++ b/workshop/gamemodes/cinema_modded/gamemode/extensions/sh_util.lua
@@ -50,4 +50,106 @@ function ParseElementContent( element )
local output = element:gsub( "^%s-<%w->%s-", "" )
-- Trim end
return output:gsub( "%s-%w->%s-$", "" )
+end
+
+
+local countrys = {
+ DM="Dominica",IO="British Indian Ocean Territory",
+ FM="Micronesia, Federated States of",AM="Armenia",
+ JO="Jordan",CM="Cameroon",
+ BM="Bermuda",FO="Faroe Islands",
+ AO="Angola",DO="Dominican Republic",
+ BO="Bolivia, Plurinational State of",TK="Tokelau",
+ ZM="Zambia",CO="Colombia",
+ TM="Turkmenistan",RS="Serbia",
+ MS="Montserrat",PS="Palestine, State of",
+ PM="Saint Pierre and Miquelon",SM="San Marino",
+ MM="Myanmar",WS="Samoa",
+ TO="Tonga",BQ="Bonaire, Sint Eustatius and Saba",
+ RO="Romania",MO="Macao",
+ GQ="Equatorial Guinea",SO="Somalia",
+ AQ="Antarctica",BS="Bahamas",
+ TW="Taiwan, Province of China",AS="American Samoa",
+ MW="Malawi",IQ="Iraq",
+ ZW="Zimbabwe",ES="Spain",
+ GU="Guam",YE="Yemen",
+ AU="Australia",IS="Iceland",
+ LS="Lesotho",SZ="Swaziland",
+ ZA="South Africa",LU="Luxembourg",
+ AW="Aruba",NU="Niue",
+ GW="Guinea-Bissau",BW="Botswana",
+ VI="Virgin Islands, U.S.",BH="Bahrain",
+ CW="Curaçao",PY="Paraguay",
+ VE="Venezuela, Bolivarian Republic of",KY="Cayman Islands",
+ VU="Vanuatu",UZ="Uzbekistan",LY="Libya",
+ UY="Uruguay",KW="Kuwait",
+ UM="United States Minor Outlying Islands",US="United States",
+ CY="Cyprus",BY="Belarus",PH="Philippines",
+ AE="United Arab Emirates",GY="Guyana",
+ UA="Ukraine",BB="Barbados",UG="Uganda",
+ TV="Tuvalu",TC="Turks and Caicos Islands",
+ PR="Puerto Rico",GB="United Kingdom",
+ PN="Pitcairn",GD="Grenada",
+ TG="Togo",AD="Andorra",
+ SH="Saint Helena, Ascension and Tristan da Cunha",
+ CD="Congo, the Democratic Republic of the",
+ TH="Thailand",TZ="Tanzania, United Republic of",
+ LB="Lebanon",TJ="Tajikistan",
+ AF="Afghanistan",ID="Indonesia",
+ GF="French Guiana",SY="Syrian Arab Republic",
+ SS="South Sudan",BF="Burkina Faso",
+ CF="Central African Republic",SE="Sweden",
+ KH="Cambodia",NF="Norfolk Island",
+ EH="Western Sahara",SD="Sudan",
+ VC="Saint Vincent and the Grenadines",MD="Moldova, Republic of",
+ SR="Suriname",LK="Sri Lanka",CH="Switzerland",
+ GS="South Georgia and the South Sandwich Islands",WF="Wallis and Futuna",
+ TF="French Southern Territories",GH="Ghana",SB="Solomon Islands",
+ SI="Slovenia",PF="French Polynesia",SK="Slovakia",
+ SX="Sint Maarten (Dutch part)",BJ="Benin",IL="Israel",
+ NL="Netherlands",SG="Singapore",FJ="Fiji",
+ RU="Russian Federation",DJ="Djibouti",GL="Greenland",
+ IN="India",AL="Albania",SC="Seychelles",
+ CL="Chile",SN="Senegal",SA="Saudi Arabia",
+ BL="Saint Barthélemy",HN="Honduras",BI="Burundi",
+ SJ="Svalbard and Jan Mayen",GN="Guinea",MF="Saint Martin (French part)",
+ LC="Saint Lucia",BN="Brunei Darussalam",CN="China",
+ KN="Saint Kitts and Nevis",TL="Timor-Leste",MR="Mauritania",
+ RW="Rwanda",SL="Sierra Leone",PL="Poland",ML="Mali",
+ EE="Estonia",QA="Qatar",TR="Turkey",VN="Viet Nam",
+ TT="Trinidad and Tobago",TN="Tunisia",GP="Guadeloupe",
+ ST="Sao Tome and Principe",PT="Portugal",MT="Malta",
+ KP="Korea, Democratic People's Republic of",PG="Papua New Guinea",
+ BR="Brazil",JP="Japan",GG="Guernsey",
+ AR="Argentina",FR="France",GR="Greece",
+ SV="El Salvador",ER="Eritrea",PW="Palau",
+ KR="Korea, Republic of",HR="Croatia",
+ IR="Iran, Islamic Republic of",NR="Nauru",
+ PK="Pakistan",LR="Liberia",HU="Hungary",
+ LT="Lithuania",IT="Italy",MX="Mexico",
+ NO="Norway",HT="Haiti",BV="Bouvet Island",
+ CV="Cape Verde",MP="Northern Mariana Islands",BD="Bangladesh",
+ MZ="Mozambique",TD="Chad",LV="Latvia",NI="Nicaragua",
+ NZ="New Zealand",AT="Austria",NP="Nepal",CX="Christmas Island",
+ MV="Maldives",MK="Macedonia, the Former Yugoslav Republic of",KZ="Kazakhstan",
+ ME="Montenegro",MN="Mongolia",AX="Åland Islands",
+ FK="Falkland Islands (Malvinas)",CA="Canada",BA="Bosnia and Herzegovina",
+ BZ="Belize",CZ="Czech Republic",GA="Gabon",AZ="Azerbaijan",
+ YT="Mayotte",MU="Mauritius",DZ="Algeria",MQ="Martinique",
+ CC="Cocos (Keeling) Islands",LA="Lao People's Democratic Republic",MH="Marshall Islands",
+ NA="Namibia",MY="Malaysia",PA="Panama",
+ DE="Germany",GE="Georgia",MA="Morocco",
+ NC="New Caledonia",DK="Denmark",VA="Holy See (Vatican City State)",
+ BE="Belgium",BT="Bhutan",MC="Monaco",AG="Antigua and Barbuda",
+ NE="Niger",IE="Ireland",BG="Bulgaria",KE="Kenya",JE="Jersey",
+ CG="Congo",NG="Nigeria",GM="Gambia",KI="Kiribati",CU="Cuba",
+ PE="Peru",LI="Liechtenstein",RE="Réunion",KG="Kyrgyzstan",
+ VG="Virgin Islands, British",EG="Egypt",CI="Côte d'Ivoire",HK="Hong Kong",
+ ET="Ethiopia",MG="Madagascar",GI="Gibraltar",FI="Finland",AI="Anguilla",
+ EC="Ecuador",GT="Guatemala",OM="Oman",CK="Cook Islands",IM="Isle of Man",
+ HM="Heard Island and McDonald Islands",KM="Comoros",JM="Jamaica",CR="Costa Rica"
+}
+
+function getCountryName(letter)
+ return (letter and countrys[letter] and countrys[letter]) or "Unkown"
end
\ No newline at end of file
diff --git a/workshop/gamemodes/cinema_modded/gamemode/modules/scoreboard/cl_queue.lua b/workshop/gamemodes/cinema_modded/gamemode/modules/scoreboard/cl_queue.lua
index 879d6b1..9f9ef73 100644
--- a/workshop/gamemodes/cinema_modded/gamemode/modules/scoreboard/cl_queue.lua
+++ b/workshop/gamemodes/cinema_modded/gamemode/modules/scoreboard/cl_queue.lua
@@ -60,6 +60,13 @@ function QUEUE:Init()
end
self.Options:AddItem(RefreshButton)
+ local InstanceButton = vgui.Create( "TheaterButton" )
+ InstanceButton:SetText( "YouTube Instance Switcher" )
+ InstanceButton.DoClick = function(self)
+ RunConsoleCommand( "cinema_invidious_switch" )
+ end
+ self.Options:AddItem(InstanceButton)
+
end
function QUEUE:AddVideo( vid )
diff --git a/workshop/gamemodes/cinema_modded/gamemode/modules/theater/services/sh_youtube.lua b/workshop/gamemodes/cinema_modded/gamemode/modules/theater/services/sh_youtube.lua
index 9923734..fb96cf3 100644
--- a/workshop/gamemodes/cinema_modded/gamemode/modules/theater/services/sh_youtube.lua
+++ b/workshop/gamemodes/cinema_modded/gamemode/modules/theater/services/sh_youtube.lua
@@ -6,94 +6,83 @@ SERVICE.IsTimed = true
SERVICE.Dependency = DEPENDENCY_PARTIAL
SERVICE.ExtentedVideoInfo = true
-local METADATA_URL = "https://www.youtube.com/watch?v=%s"
-
---[[
- Credits to veitikka (https://github.com/veitikka) for fixing YouTube service and writing the
- Workaround with a Metadata parser.
---]]
-
--- Lua search patterns to find metadata from the html
-local patterns = {
- ["title"] = "",
- ["title_fallback"] = "
.-",
- ["duration"] = "",
- ["live"] = "",
- ["live_enddate"] = "",
- ["age_restriction"] = ""
-}
-
----
--- Function to parse video metadata straight from the html instead of using the API
---
-local function ParseMetaDataFromHTML( html )
- --MetaData table to return when we're done
- local metadata, html = {}, html
-
- -- Fetch title, with fallbacks if needed
- metadata.title = util.ParseElementAttribute(html:match(patterns["title"]), "content")
- or util.ParseElementContent(html:match(patterns["title_fallback"]))
-
- -- Parse HTML entities in the title into symbols
- metadata.title = url.htmlentities_decode(metadata.title)
-
- metadata.familyfriendly = util.ParseElementAttribute(html:match(patterns["age_restriction"]), "content") or ""
-
- -- See if the video is an ongoing live broadcast
- -- Set duration to 0 if it is, otherwise use the actual duration
- local isLiveBroadcast = tobool(util.ParseElementAttribute(html:match(patterns["live"]), "content"))
- local broadcastEndDate = html:match(patterns["live_enddate"])
- if isLiveBroadcast and not broadcastEndDate then
- -- Mark as live video
- metadata.duration = 0
- else
- local durationISO8601 = util.ParseElementAttribute(html:match(patterns["duration"]), "content")
- if isstring(durationISO8601) then
- metadata.duration = math.max(1, util.ISO_8601ToSeconds(durationISO8601))
- end
- end
-
- return metadata
-end
+local hostname = ""
function SERVICE:Match( url )
return url.host and url.host:match("youtu.?be[.com]?")
end
if (CLIENT) then
+ local THEATER_JS = [[
+ var checkerInterval = setInterval(function() {
+ var player = document.getElementsByTagName("VIDEO")[0]
+ if (!!player) {
+ if (player.paused) {player.play();}
+ if (player.paused === false && player.readyState === 4) {
+ clearInterval(checkerInterval);
+
+ window.cinema_controller = player;
+ player.style = "width:100%; height: 100%;";
+
+ exTheater.controllerReady();
+ }
+ }
+ }, 50);
+ ]]
+
+ local embedUrlParser = {
+ ["youtube"] = function( Video )
+ return ("https://" .. hostname .. "/embed/%s?autoplay=0&thin_mode=1&controls=0&quality=auto&volume=1&t=%s"):format(
+ Video:Data(),
+ math.Round(CurTime() - Video:StartTime()
+ )
+ )
+ end,
+ ["youtubelive"] = function( Video )
+ return ("https://" .. hostname .. "/embed/%s?autoplay=0&thin_mode=1&controls=0&quality=auto&volume=1"):format(
+ Video:Data()
+ )
+ end
+ }
function SERVICE:LoadProvider( Video, panel )
- local url = GetGlobal2String( "cinema_url", "" ) .. "youtube.html?v=%s"
- panel:OpenURL( url:format( Video:Data() ) ..
- (self.IsTimed and ("&t=%s"):format(
- math.Round(CurTime() - Video:StartTime())
- ) or "")
- )
-
+ panel:OpenURL( embedUrlParser[Video:Type()](Video) )
panel.OnDocumentReady = function(pnl)
- self:LoadExFunctions(pnl)
+ self:LoadExFunctions( pnl )
+ pnl:QueueJavascript(THEATER_JS)
end
+
end
function SERVICE:GetMetadata( data, callback )
+ local url = ("https://%s/api/v1/videos/%s"):format(hostname, data)
- http.Fetch(METADATA_URL:format(data), function(body, length, headers, code)
+ http.Fetch(url, function(body, length, headers, code)
if not body or code ~= 200 then
- callback({ err = ("Not expected response received from YouTube (Code: %d)"):format(code) })
+ callback({ err = ("Not expected response received from API (Code: %d, Try diffrent Instance)"):format(code) })
return
end
- local status, metadata = pcall(ParseMetaDataFromHTML, body)
- if not status then
+ local response = util.JSONToTable(body)
+ if not response then
callback({ err = "Failed to parse MetaData from YouTube" })
return
end
- callback(metadata)
+ callback({
+ title = response.title,
+ premium = response.premium,
+ lengthSeconds = response.lengthSeconds,
+ isListed = response.isListed,
+ liveNow = response.liveNow,
+ isUpcoming = response.isUpcoming,
+ isFamilyFriendly = response.isFamilyFriendly,
+ })
+
end, function(error)
- callback({ err = ("YouTube Error: %s"):format(error) })
+ callback({ err = ("YouTube Error: %s, Try diffrent Instance"):format(error) })
end, {})
end
end
@@ -140,19 +129,31 @@ function SERVICE:GetVideoInfo( data, onSuccess, onFailure )
return onFailure(metadata.err)
end
+ if not metadata.isListed then
+ return onFailure( "Service_EmbedDisabled" )
+ end
+
+ if metadata.premium then
+ return onFailure( "Service_PurchasableContent" )
+ end
+
+ if metadata.prisUpcomingemium then
+ return onFailure( "Service_StreamOffline" )
+ end
+
local info = {}
info.title = metadata.title
info.thumbnail = ("https://img.youtube.com/vi/(%s)/hqdefault.jpg"):format(data)
- if metadata.duration == 0 then
+ if (metadata.liveNow and metadata.lengthSeconds == 0) then
info.type = "youtubelive"
info.duration = 0
else
- if metadata.familyfriendly == "18+" then
+ if not metadata.isFamilyFriendly then
info.type = "youtubensfw"
end
- info.duration = metadata.duration
+ info.duration = metadata.lengthSeconds
end
if onSuccess then
@@ -173,4 +174,133 @@ theater.RegisterService( "youtubelive", {
Dependency = DEPENDENCY_COMPLETE,
Hidden = true,
LoadProvider = CLIENT and SERVICE.LoadProvider or function() end
-} )
\ No newline at end of file
+} )
+
+
+do
+ if not (CLIENT) then return end
+
+ local instances, description = {}, "Invidious is a de-googled alternative to YouTube, it allows you to watch videos without ads and restrictions. It reduces the data sent to Google when watching videos."
+ local cInstance = CreateClientConVar("cinema_invidious_instance", "invidious.fdn.fr", true, false)
+
+ hostname = cInstance:GetString()
+
+ cvars.AddChangeCallback(cInstance:GetName(), function(convar, oldValue, newValue)
+ hostname = newValue
+ end, cInstance:GetName())
+
+ do -- Invidious Switcher menu
+ local function createButton(parent, pos, size, text, callback )
+ local button = vgui.Create( "DButton", parent )
+ button:SetPos( pos[1], pos[2] )
+ button:SetSize( size[1], size[2] )
+ button:SetText(text)
+ button:SizeToContents()
+ button.DoClick = callback
+
+ return button
+ end
+
+ local function switcher()
+ local Frame = vgui.Create( "DFrame" )
+ Frame:SetTitle("(YouTube) Invidious Instance Switcher")
+ Frame:SetSize( 500, 500 )
+ Frame:Center()
+ Frame:MakePopup()
+
+ do -- Top Box
+ local SettingsBox = vgui.Create( "DPanel", Frame )
+ SettingsBox:Dock(TOP)
+ SettingsBox:SetHeight(50)
+ SettingsBox:SetBackgroundColor(Color(255,255,255, 0))
+
+ local Description = vgui.Create( "RichText", SettingsBox )
+ Description:Dock(FILL)
+ Description:SetText( description )
+ end
+
+ do -- Instance list
+ local InstanceList = vgui.Create( "DListView", Frame )
+ InstanceList:Dock( FILL )
+ InstanceList:SetMultiSelect( false )
+ InstanceList:SetSortable( true )
+
+ InstanceList:AddColumn( "Instance" )
+ InstanceList:AddColumn( "Users" )
+ InstanceList:AddColumn( "Location" )
+ InstanceList:AddColumn( "Health" )
+
+ function InstanceList:DoDoubleClick(lineID, line)
+ cInstance:SetString( line:GetColumnText(1) )
+
+ if theater.ActivePanel() then
+ theater.RefreshPanel(true)
+ end
+ end
+
+ local lines = {}
+ for host,tbl in pairs(instances) do
+ if tbl["api"] then
+ lines[host] = InstanceList:AddLine(host, tbl["users"], tbl["region"], tbl["uptime"])
+ end
+ end
+
+ InstanceList:SortByColumn( 2, true ) -- Sort by Users count
+
+ if IsValid(lines[hostname]) then
+ InstanceList:SelectItem( lines[hostname] )
+ end
+
+ end
+
+ end
+ concommand.Add("cinema_invidious_switch", switcher, nil, "Switch the Invidious instance")
+ end
+
+ do -- Instance fetcher & updater
+ local function fetchInstances()
+ local function onSuccess(body, length, headers, code)
+ if not body or code ~= 200 then return end
+
+ local response = util.JSONToTable(body)
+ if not response then return end
+
+ instances = {} -- Clear instance list
+
+ for k,v in pairs(response) do
+ local name, tbl = v[1], v[2]
+
+ if tbl.type ~= "https" then
+ continue
+ end
+
+ local api = tbl["api"]
+ local region = tbl["region"]
+ local users = (tbl["stats"] and tbl["stats"]["usage"] and tbl["stats"]["usage"]["users"] and tbl["stats"]["usage"]["users"]["total"] or "-")
+ local uptime = (tbl["monitor"] and tbl["monitor"]["uptime"] and tbl["monitor"]["uptime"] or "-")
+
+ instances[name] = {
+ api = api,
+ region = util.getCountryName(region),
+ users = users,
+ uptime = uptime,
+ }
+
+ end
+ end
+
+ local function onFailure(message)
+ print("[Invidious API]: " .. message)
+ end
+
+ http.Fetch("https://api.invidious.io/instances.json?sort_by=type,users", onSuccess, onFailure, {
+ ["Accept-Encoding"] = "gzip, deflate",
+ ["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36",
+ })
+ end
+ fetchInstances()
+
+ if timer.Exists("Invidious.Update") then timer.Remove("Invidious.Update") end
+ timer.Create("Invidious.Update", 300, 0, fetchInstances)
+ end
+end
\ No newline at end of file