Skip to content

Commit

Permalink
Add serverside cvar to use Invidious for YouTube
Browse files Browse the repository at this point in the history
  • Loading branch information
Myaats committed Aug 3, 2024
1 parent c9d97a1 commit 613f2c9
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 50 deletions.
32 changes: 20 additions & 12 deletions lua/mediaplayer/services/youtube/cl_init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,22 @@ local JS_Seek = "if(window.MediaPlayer) MediaPlayer.seek(%s);"
local JS_Play = "if(window.MediaPlayer) MediaPlayer.play();"
local JS_Pause = "if(window.MediaPlayer) MediaPlayer.pause();"

local function YTSetVolume( self )
-- if not self.playerId then return end
local js = JS_SetVolume:format( MediaPlayer.Volume() * 100 )
if self.Browser then
self.Browser:RunJavascript(js)
end
end
-- VideoJS for Invidious embeds
local VJS_SetVolume = "if(player) player.volume(%s / 100);"
local VJS_Seek = "var time = %s; if(player && !player.seeking() && Math.abs(player.cache_.currentTime - time) > 5) player.currentTime(time);"
local VJS_Play = "if(player) player.play();"
local VJS_Pause = "if(player) player.pause();"

local function YTSeek( self, seekTime )
-- if not self.playerId then return end
local js = JS_Seek:format( seekTime )
local js = (self:IsInvidiousEmbed() and VJS_Seek or JS_Seek):format( seekTime )
if self.Browser then
self.Browser:RunJavascript(js)
end
end

function SERVICE:SetVolume( volume )
local js = JS_SetVolume:format( (volume or MediaPlayer.Volume()) * 100 )
local js = (self:IsInvidiousEmbed() and VJS_SetVolume or JS_SetVolume):format( (volume or MediaPlayer.Volume()) * 100 )
self.Browser:RunJavascript(js)
end

Expand All @@ -42,17 +40,23 @@ function SERVICE:OnBrowserReady( browser )

-- Resume paused player
if self._YTPaused then
self.Browser:RunJavascript( JS_Play )
local js = JS_Play
if self:IsInvidiousEmbed() then js = VJS_Play end
self.Browser:RunJavascript( js )
self._YTPaused = nil
return
end

local url_prefix = htmlBaseUrl .. "youtube.html"
local invidious_cvar_sv = GetConVar("mediaplayer_invidious_instance_sv")
if cvInvidiousEnable:GetBool() then
url_prefix = htmlBaseUrl .. "invidious.html"
elseif invidious_cvar_sv:GetString():Trim() ~= "" then
url_prefix = invidious_cvar_sv:GetString() .. "/embed/" .. self:GetYouTubeVideoId()
self._InvidiousEmbed = true
end

local url = url_prefix ..
local url = url_prefix ..
'?v=' .. self:GetYouTubeVideoId() ..
'&timed=' .. (self:IsTimed() and '1' or '0') ..
'&instance=' .. cvInvidiousInstance:GetString() ..
Expand All @@ -73,7 +77,7 @@ function SERVICE:Pause()
BaseClass.Pause( self )

if ValidPanel(self.Browser) then
self.Browser:RunJavascript(JS_Pause)
self.Browser:RunJavascript(self:IsInvidiousEmbed() and VJS_Pause or JS_Pause)
self._YTPaused = true
end
end
Expand All @@ -88,3 +92,7 @@ end
function SERVICE:IsMouseInputEnabled()
return IsValid( self.Browser )
end

function SERVICE:IsInvidiousEmbed()
return self._InvidiousEmbed
end
125 changes: 87 additions & 38 deletions lua/mediaplayer/services/youtube/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ include "shared.lua"
local TableLookup = MediaPlayerUtils.TableLookup
local htmlentities_decode = url.htmlentities_decode

local invidious_instance = CreateConVar( "mediaplayer_invidious_instance_sv", "", {
FCVAR_ARCHIVE,
FCVAR_NOTIFY,
FCVAR_REPLICATED,
FCVAR_SERVER_CAN_EXECUTE
}, "Serverside invidious instance, will be used clientside too" )

---
-- Helper function for converting ISO 8601 time strings; this is the formatting
-- used for duration specified in the YouTube v3 API.
Expand Down Expand Up @@ -80,45 +87,87 @@ function SERVICE:GetMetadata( callback )

else
local videoId = self:GetYouTubeVideoId()
local videoUrl = "https://invidious.fdn.fr/api/v1/videos/" .. videoId

self:Fetch( videoUrl,
-- On Success
function( body, length, headers, code )
local metadata, data = {}, util.JSONToTable(body)

metadata.title = data.title
metadata.thumbnail = data.videoThumbnails[1].url
metadata.duration = not data.liveNow and data.lengthSeconds or 0

-- html couldn't be parsed
if not metadata.title or not isnumber(metadata.duration) then
-- Title is nil or Duration is nan
if istable(metadata) then
metadata = "title = "..type(metadata.title)..", duration = "..type(metadata.duration)

local instance = invidious_instance:GetString()
-- Use invidious serverside
if instance:Trim() ~= "" then
local videoUrl = instance .. "/api/v1/videos/" .. videoId

self:Fetch( videoUrl,
-- On Success
function( body, length, headers, code )
local metadata, data = {}, util.JSONToTable(body)

metadata.title = data.title
metadata.thumbnail = data.videoThumbnails[1].url
metadata.duration = not data.liveNow and data.lengthSeconds or 0

-- html couldn't be parsed
if not metadata.title or not isnumber(metadata.duration) then
-- Title is nil or Duration is nan
if istable(metadata) then
metadata = "title = "..type(metadata.title)..", duration = "..type(metadata.duration)
end
-- Misc error
callback(false, "Failed to parse HTML Page for metadata: "..metadata)
return
end

self:SetMetadata(metadata, true)

if self:IsTimed() then
MediaPlayer.Metadata:Save(self)
end

callback(self._metadata)
end,
-- On failure
function( reason )
callback(false, "Failed to fetch YouTube HTTP metadata [reason="..tostring(reason).."]")
end,
-- Headers
{
["User-Agent"] = "Googlebot"
}
)
-- Use YouTube serverside
else
local videoUrl = "https://www.youtube.com/watch?v="..videoId

self:Fetch( videoUrl,
-- On Success
function( body, length, headers, code )
local status, metadata = pcall(self.ParseYTMetaDataFromHTML, self, body)

-- html couldn't be parsed
if not status or not metadata.title or not isnumber(metadata.duration) then
-- Title is nil or Duration is nan
if istable(metadata) then
metadata = "title = "..type(metadata.title)..", duration = "..type(metadata.duration)
end
-- Misc error
callback(false, "Failed to parse HTML Page for metadata: "..metadata)
return
end

self:SetMetadata(metadata, true)

if self:IsTimed() then
MediaPlayer.Metadata:Save(self)
end
-- Misc error
callback(false, "Failed to parse HTML Page for metadata: "..metadata)
return
end

self:SetMetadata(metadata, true)

if self:IsTimed() then
MediaPlayer.Metadata:Save(self)
end

callback(self._metadata)
end,
-- On failure
function( reason )
callback(false, "Failed to fetch YouTube HTTP metadata [reason="..tostring(reason).."]")
end,
-- Headers
{
["User-Agent"] = "Googlebot"
}
)

callback(self._metadata)
end,
-- On failure
function( reason )
callback(false, "Failed to fetch YouTube HTTP metadata [reason="..tostring(reason).."]")
end,
-- Headers
{
["User-Agent"] = "Googlebot"
}
)
end
end
end

Expand Down

0 comments on commit 613f2c9

Please sign in to comment.