dobrograd-13-06-2022/garrysmod/addons/admin-sg/lua/modules/cl_medialib.lua
Jonny_Bro (Nikita) e4d5311906 first commit
2023-11-16 15:01:19 +05:00

1310 lines
47 KiB
Lua

local medialib
do
-- Note: build file expects these exact lines for them to be automatically replaced, so please don't change anything
local VERSION = "git@8c99637e"
local DISTRIBUTABLE = true
medialib = {}
medialib.VERSION = VERSION
medialib.DISTRIBUTABLE = DISTRIBUTABLE
medialib.Modules = {}
local cvar_debug = CreateConVar("medialib_debug", "0", FCVAR_ARCHIVE)
cvars.AddChangeCallback(cvar_debug:GetName(), function(_, _, val)
medialib.DEBUG = val == "1"
end)
medialib.DEBUG = cvar_debug:GetBool()
function medialib.modulePlaceholder(name)
medialib.Modules[name] = {}
end
function medialib.module(name, opts)
if medialib.DEBUG then
print("[MediaLib] Creating module " .. name)
end
local mod = medialib.Modules[name] or {
name = name,
options = opts,
}
medialib.Modules[name] = mod
return mod
end
-- AddCSLuaFile all medialib modules
if SERVER then
for _,fname in pairs(file.Find("medialib/*", "LUA")) do
AddCSLuaFile("medialib/" .. fname)
end
end
local file_Exists = file.Exists
function medialib.tryInclude(file)
if file_Exists(file, "LUA") then
include(file)
return true
end
if medialib.DEBUG then
print("[MediaLib] Attempted to include nonexistent file " .. file)
end
return false
end
function medialib.load(name)
local mod = medialib.Modules[name]
if mod then return mod end
if medialib.DEBUG then
print("[MediaLib] Loading unreferenced module " .. name)
end
local file = "medialib/" .. name .. ".lua"
if not medialib.tryInclude(file) then return nil end
return medialib.Modules[name]
end
local medialibg = setmetatable({medialib = medialib}, {__index = _G})
local real_file_meta = {
read = function(self)
return file.Read(self.lua_path, "LUA")
end,
load = function(self)
--local str = self:read()
--if not str then error("MedialibDynLoad: could not load " .. self.lua_path) end
-- TODO this does not function correctly; embedded medialib loading real_file will use global medialib as its 'medialib' instance
return include(self.lua_path)
end,
addcs = function(self)
AddCSLuaFile(self.lua_path)
end,
}
real_file_meta.__index = real_file_meta
local virt_file_meta = {
read = function(self)
return self.source
end,
load = function(self)
local compiled = CompileString(self:read(), "MediaLib_DynFile_" .. self.name)
setfenv(compiled, medialibg)
return compiled()
end,
addcs = function() end
}
virt_file_meta.__index = virt_file_meta
-- Used for medialib packed into a single file
medialib.FolderItems = {}
-- Returns an iterator for files in folder
function medialib.folderIterator(folder)
local files = {}
for _,fname in pairs(file.Find("medialib/" .. folder .. "/*.lua", "LUA")) do
table.insert(files, setmetatable({
name = fname,
lua_path = "medialib/" .. folder .. "/" .. fname
}, real_file_meta))
end
for k,item in pairs(medialib.FolderItems) do
local mfolder = k:match("^([^/]*).+")
if mfolder == folder then
table.insert(files, setmetatable({
name = k:match("^[^/]*/(.+)"),
source = item
}, virt_file_meta))
end
end
return pairs(files)
end
if CLIENT then
local function Rainbow()
for i=1, 30 do
MsgC(HSVToColor(30*i, 0.5, 0.9), " " .. string.rep("SEE BELOW FOR INSTRUCTIONS ", 3) .. "\n")
end
end
concommand.Add("medialib_noflash", function(_, _, args)
if args[1] == "rainbow" then Rainbow() end
SetClipboardText("http://get.adobe.com/flashplayer/otherversions/")
MsgN("[ MediaLib: How to get Flash Player ]")
MsgN("1. Open this website in your browser (not the ingame Steam browser): http://get.adobe.com/flashplayer/otherversions/")
MsgN(" (the link has been automatically copied to your clipboard)")
MsgN("2. Download and install the NSAPI (for Firefox) version")
MsgN("3. Restart your Garry's Mod and rejoin this server")
MsgN("[ ======================= ]")
end)
concommand.Add("medialib_lowaudio", function(_, _, args)
if args[1] == "rainbow" then Rainbow() end
SetClipboardText("http://windows.microsoft.com/en-us/windows7/adjust-the-sound-level-on-your-computer")
MsgN("[ MediaLib: How to fix muted sound ]")
MsgN("1. Follow instructions here: http://windows.microsoft.com/en-us/windows7/adjust-the-sound-level-on-your-computer")
MsgN(" (the link has been automatically copied to your clipboard, you can open it in the steam ingame browser)")
MsgN("2. Increase the volume of a process called 'Awesomium Core'")
MsgN("3. You should immediately start hearing sound if a mediaclip is playing")
MsgN("[ ======================= ]")
end)
hook.Add("OnPlayerChat", "MediaLib.ShowInstructions", function(ply, text)
if text:match("!ml_noflash") then
RunConsoleCommand("medialib_noflash", "rainbow")
RunConsoleCommand("showconsole")
elseif text:match("!ml_lowvolume") then
RunConsoleCommand("medialib_lowaudio", "rainbow")
RunConsoleCommand("showconsole")
end
end)
end
end
-- 'oop'; CodeLen/MinifiedLen 2927/2927; Dependencies []
medialib.modulePlaceholder("oop")
do
local oop = medialib.module("oop")
oop.Classes = oop.Classes or {}
function oop.class(name, parent)
local cls = oop.Classes[name]
if not cls then
cls = oop.createClass(name, parent)
oop.Classes[name] = cls
if medialib.DEBUG then
print("[MediaLib] Registering oopclass " .. name)
end
end
return cls
end
function oop.resolveClass(obj)
if obj == nil then
return oop.Object
end
local t = type(obj)
if t == "string" then
local clsobj = oop.Classes[obj]
if clsobj then return clsobj end
error("Resolving class from inexistent class string '" .. tostring(obj) .. "'")
end
if t == "table" then
return obj
end
error("Resolving class from invalid object '" .. tostring(obj) .. "'")
end
-- This is a special parent used to prevent oop.Object being parent of itself
local NIL_PARENT = {}
-- Credits to Middleclass
local metamethods = {'__add', '__call', '__concat', '__div', '__ipairs', '__le',
'__len', '__lt', '__mod', '__mul', '__pairs', '__pow', '__sub',
'__tostring', '__unm'}
function oop.createClass(name, parent)
local cls = {}
-- Get parent class
local par_cls
if parent ~= NIL_PARENT then
par_cls = oop.resolveClass(parent)
end
-- Add metadata
cls.name = name
cls.super = par_cls
-- Add a subtable for class members ie methods and class/super handles
cls.members = setmetatable({}, {__index = cls.super})
-- Add built-in "keywords" that Instances can access
cls.members.class = cls
cls.members.super = cls.super
-- Instance metatable
local cls_instance_meta = {}
do
cls_instance_meta.__index = cls.members
-- Add metamethods. The class does not have members yet, so we need to use runtime lookup
for _,name in pairs(metamethods) do
cls_instance_meta[name] = function(...)
local method = cls.members[name]
if method then
return method(...)
end
end
end
end
-- Class metatable
local class_meta = {}
do
class_meta.__index = cls.members
class_meta.__newindex = cls.members
class_meta.__tostring = function(self)
return "class " .. self.name
end
-- Make the Class object a constructor.
-- ie calling Class() creates a new instance
function class_meta:__call(...)
local instance = {}
setmetatable(instance, cls_instance_meta)
-- Call constructor if exists
local ctor = instance.initialize
if ctor then ctor(instance, ...) end
return instance
end
end
-- Set meta functions
setmetatable(cls, class_meta)
return cls
end
oop.Object = oop.createClass("Object", NIL_PARENT)
-- Get the hash code ie the value Lua prints when you call __tostring()
function oop.Object:hashCode()
local meta = getmetatable(self)
local old_tostring = meta.__tostring
meta.__tostring = nil
local hash = tostring(self):match("table: 0x(.*)")
meta.__tostring = old_tostring
return hash
end
function oop.Object:__tostring()
return string.format("%s@%s", self.class.name, self:hashCode())
end
end
-- 'mediabase'; CodeLen/MinifiedLen 4305/4305; Dependencies [oop]
medialib.modulePlaceholder("mediabase")
do
local oop = medialib.load("oop")
local Media = oop.class("Media")
function Media:on(event, callback)
self._events = self._events or {}
self._events[event] = self._events[event] or {}
self._events[event][callback] = true
end
function Media:emit(event, ...)
if not self._events then return end
local callbacks = self._events[event]
if not callbacks then return end
for k,_ in pairs(callbacks) do
k(...)
end
end
function Media:getServiceBase()
error("Media:getServiceBase() not implemented!")
end
function Media:getService()
return self._service
end
function Media:getUrl()
return self._unresolvedUrl
end
-- If metadata is cached: return it
-- Otherwise either start a new metadata query, or if one is already going on
-- do nothing
function Media:lookupMetadata()
local md = self._metadata
-- Already fetched
if type(md) == "table" then return md end
-- Fetching or there was an error (TODO make error available to user)
if md == true or type(md) == "string" then return nil end
self._metadata = true
self:getService():query(self:getUrl(), function(err, data)
if err then
self._metadata = err
else
self._metadata = data
end
end)
return nil
end
-- True returned from this function does not imply anything related to how
-- ready media is to play, just that it exists somewhere in memory and should
-- at least in some point in the future be playable, but even that is not guaranteed
function Media:isValid()
return false
end
-- The GMod global IsValid requires the uppercase version
function Media:IsValid()
return self:isValid()
end
-- vol must be a float between 0 and 1
function Media:setVolume(vol) end
function Media:getVolume() end
-- "Quality" must be one of following strings: "low", "medium", "high", "veryhigh"
-- Qualities do not map equally between services (ie "low" in youtube might be "medium" in twitch)
-- Services are not guaranteed to change to the exact provided quality, or even to do anything at all
function Media:setQuality(quality) end
-- time must be an integer between 0 and duration
function Media:seek(time) end
function Media:getTime()
return 0
end
-- This method can be called repeatedly to keep the media somewhat in sync
-- with given time, which makes it a great function to keep eg. synchronized
-- televisions in sync.
function Media:sync(time, margin)
-- Only sync at most once per five seconds
if self._lastSync and self._lastSync > CurTime() - 5 then
return
end
local shouldSync = self:shouldSync(time, margin)
if not shouldSync then return end
self:seek(time + 0.1) -- Assume 0.1 sec loading time
self._lastSync = CurTime()
end
function Media:shouldSync(time, margin)
-- Check for invalid syncing state
if not self:isValid() or not self:isPlaying() then
return false
end
margin = margin or 2
local curTime = self:getTime()
local diff = math.abs(curTime - time)
return diff > margin
end
-- Must return one of following strings: "error", "loading", "buffering", "playing", "paused", "ended"
-- Can also return nil if state is unknown or cannot be represented properly
-- If getState does not return nil, it should be assumed to be the correct current state
function Media:getState() end
-- Simplified function of above; simply returns boolean indicating playing state
function Media:isPlaying()
return self:getState() == "playing"
end
function Media:play() end
function Media:pause() end
function Media:stop() end
-- Queue a function to run after media is loaded. The function should run immediately
-- if media is already loaded.
function Media:runCommand(fn) end
function Media:draw(x, y, w, h) end
function Media:getTag() return self._tag end
function Media:setTag(tag) self._tag = tag end
function Media:guessDefaultTag()
for i=1, 10 do
local info = debug.getinfo(i, "S")
if not info then break end
local src = info.short_src
local addon = src:match("addons/(.-)/")
if addon and addon ~= "medialib" then return string.format("addon:%s", addon) end
end
return "addon:medialib"
end
function Media:setDefaultTag()
self:setTag(self:guessDefaultTag())
end
function Media:getDebugInfo()
return string.format("[%s] Media [%s] valid:%s state:%s url:%s time:%d", self:getTag(), self.class.name, tostring(self:isValid()), self:getState(), self:getUrl(), self:getTime())
end
end
-- 'media'; CodeLen/MinifiedLen 746/746; Dependencies []
medialib.modulePlaceholder("media")
do
local media = medialib.module("media")
media.Services = {}
function media.registerService(name, cls)
media.Services[name] = cls()
end
media.RegisterService = media.registerService -- alias
function media.service(name)
return media.Services[name]
end
media.Service = media.service -- alias
function media.guessService(url, opts)
for name,service in pairs(media.Services) do
local isViable = true
if opts and opts.whitelist then
isViable = isViable and table.HasValue(opts.whitelist, name)
end
if opts and opts.blacklist then
isViable = isViable and not table.HasValue(opts.blacklist, name)
end
if isViable and service:isValidUrl(url) then
return service
end
end
end
media.GuessService = media.guessService -- alias
end
-- 'mediaregistry'; CodeLen/MinifiedLen 1248/1248; Dependencies []
medialib.modulePlaceholder("mediaregistry")
do
local mediaregistry = medialib.module("mediaregistry")
local cache = setmetatable({}, {__mode = "v"})
function mediaregistry.add(media)
table.insert(cache, media)
end
function mediaregistry.get()
return cache
end
concommand.Add("medialib_listall", function()
hook.Run("MediaLib_ListAll")
end)
local vers = medialib.VERSION
hook.Add("MediaLib_ListAll", "MediaLib_" .. vers, function()
print("Media for medialib version " .. vers .. ":")
for _,v in pairs(cache) do
print(v:getDebugInfo())
end
end)
concommand.Add("medialib_stopall", function()
hook.Run("MediaLib_StopAll")
end)
hook.Add("MediaLib_StopAll", "MediaLib_" .. medialib.VERSION, function()
for _,v in pairs(cache) do
v:stop()
end
table.Empty(cache)
end)
local cvar_debug = CreateConVar("medialib_debugmedia", "0")
hook.Add("HUDPaint", "MediaLib_G_DebugMedia", function()
if not cvar_debug:GetBool() then return end
local counter = {0}
hook.Run("MediaLib_DebugPaint", counter)
end)
hook.Add("MediaLib_DebugPaint", "MediaLib_" .. medialib.VERSION, function(counter)
local i = counter[1]
for _,media in pairs(cache) do
local t = string.format("#%d %s", i, media:getDebugInfo())
draw.SimpleText(t, "DermaDefault", 10, 10 + i*15)
i=i+1
end
counter[1] = i
end)
end
-- 'servicebase'; CodeLen/MinifiedLen 2234/2234; Dependencies [oop,mediaregistry]
medialib.modulePlaceholder("servicebase")
do
local oop = medialib.load("oop")
local mediaregistry = medialib.load("mediaregistry")
local Service = oop.class("Service")
function Service:on(event, callback)
self._events = {}
self._events[event] = self._events[event] or {}
self._events[event][callback] = true
end
function Service:emit(event, ...)
for k,_ in pairs(self._events[event] or {}) do
k(...)
end
if event == "error" then
MsgN("[MediaLib] Video error: " .. table.ToString{...})
end
end
function Service:load(url, opts) end
function Service:loadMediaObject(media, url, opts)
media._unresolvedUrl = url
media._service = self
media:setDefaultTag()
hook.Run("Medialib_ProcessOpts", media, opts or {})
mediaregistry.add(media)
self:resolveUrl(url, function(resolvedUrl, resolvedData)
media:openUrl(resolvedUrl)
if resolvedData and resolvedData.start and (not opts or not opts.dontSeek) then media:seek(resolvedData.start) end
end)
end
function Service:isValidUrl(url) end
-- Sub-services should override this
function Service:directQuery(url, callback) end
-- A metatable for the callback chain
local _service_cbchain_meta = {}
_service_cbchain_meta.__index = _service_cbchain_meta
function _service_cbchain_meta:addCallback(cb)
table.insert(self._callbacks, cb)
end
function _service_cbchain_meta:run(err, data)
local first = table.remove(self._callbacks, 1)
if not first then return end
first(err, data, function(err, data)
self:run(err, data)
end)
end
-- Query calls direct query and then passes the data through a medialib hook
function Service:query(url, callback)
local cbchain = setmetatable({_callbacks = {}}, _service_cbchain_meta)
-- First add the data gotten from the service itself
cbchain:addCallback(function(_, _, cb) return self:directQuery(url, cb) end)
-- Then add custom callbacks
hook.Run("Medialib_ExtendQuery", url, cbchain)
-- Then add the user callback
cbchain:addCallback(function(err, data) callback(err, data) end)
-- Finally run the chain
cbchain:run(url)
end
function Service:parseUrl(url) end
-- the second argument to cb() function call has some standard keys:
-- `start` the time at which to start media in seconds
function Service:resolveUrl(url, cb)
cb(url, self:parseUrl(url))
end
end
-- 'timekeeper'; CodeLen/MinifiedLen 1016/1016; Dependencies [oop]
medialib.modulePlaceholder("timekeeper")
do
-- The purpose of TimeKeeper is to keep time where it is not easily available synchronously (ie. HTML based services)
local oop = medialib.load("oop")
local TimeKeeper = oop.class("TimeKeeper")
function TimeKeeper:initialize()
self:reset()
end
function TimeKeeper:reset()
self.cachedTime = 0
self.running = false
self.runningTimeStart = 0
end
function TimeKeeper:getTime()
local time = self.cachedTime
if self.running then
time = time + (RealTime() - self.runningTimeStart)
end
return time
end
function TimeKeeper:isRunning()
return self.running
end
function TimeKeeper:play()
if self.running then return end
self.runningTimeStart = RealTime()
self.running = true
end
function TimeKeeper:pause()
if not self.running then return end
local runningTime = RealTime() - self.runningTimeStart
self.cachedTime = self.cachedTime + runningTime
self.running = false
end
function TimeKeeper:seek(time)
self.cachedTime = time
if self.running then
self.runningTimeStart = RealTime()
end
end
end
-- 'service_html'; CodeLen/MinifiedLen 7390/7390; Dependencies [oop,timekeeper]
medialib.modulePlaceholder("service_html")
do
local oop = medialib.load("oop")
medialib.load("timekeeper")
local HTMLService = oop.class("HTMLService", "Service")
function HTMLService:load(url, opts)
local media = oop.class("HTMLMedia")()
self:loadMediaObject(media, url, opts)
return media
end
-- Whether or not we can trust that the HTML panel will send 'playing', 'paused'
-- and other playback related events. If this returns true, 'timekeeper' will
-- not be updated in playback related methods (except stop).
function HTMLService:hasReliablePlaybackEvents(media)
return false
end
local AwesomiumPool = {instances = {}}
concommand.Add("medialib_awepoolinfo", function()
print("AwesomiumPool> Free instance count: " .. #AwesomiumPool.instances)
end)
-- If there's bunch of awesomium instances in pool, we clean one up every 30 seconds
timer.Create("MediaLib.AwesomiumPoolCleaner", 30, 0, function()
if #AwesomiumPool.instances < 3 then return end
local inst = table.remove(AwesomiumPool.instances, 1)
if IsValid(inst) then inst:Remove() end
end)
function AwesomiumPool.get()
local inst = table.remove(AwesomiumPool.instances, 1)
if not IsValid(inst) then
local pnl = vgui.Create("DHTML")
return pnl
end
return inst
end
function AwesomiumPool.free(inst)
if not IsValid(inst) then return end
inst:SetHTML("")
table.insert(AwesomiumPool.instances, inst)
end
local cvar_showAllMessages = CreateConVar("medialib_showallmessages", "0")
local HTMLMedia = oop.class("HTMLMedia", "Media")
local panel_width, panel_height = 1280, 720
function HTMLMedia:initialize()
self.timeKeeper = oop.class("TimeKeeper")()
self.panel = AwesomiumPool.get()
local pnl = self.panel
pnl:SetPos(0, 0)
pnl:SetSize(panel_width, panel_height)
local hookid = "MediaLib.HTMLMedia.FakeThink-" .. self:hashCode()
hook.Add("Think", hookid, function()
if not IsValid(self.panel) then
hook.Remove("Think", hookid)
return
end
self.panel:Think()
end)
local oldcm = pnl._OldCM or pnl.ConsoleMessage
pnl._OldCM = oldcm
pnl.ConsoleMessage = function(pself, msg)
if msg and not cvar_showAllMessages:GetBool() then
-- Filter some things out
if string.find(msg, "XMLHttpRequest", nil, true) then return end
if string.find(msg, "Unsafe JavaScript attempt to access", nil, true) then return end
if string.find(msg, "Unable to post message to", nil, true) then return end
if string.find(msg, "ran insecure content from", nil, true) then return end
end
return oldcm(pself, msg)
end
pnl:AddFunction("console", "warn", function(param)
-- Youtube seems to spam lots of useless stuff here (that requires this function still?), so block by default
if not cvar_showAllMessages:GetBool() then return end
pnl:ConsoleMessage(param)
end)
pnl:SetPaintedManually(true)
pnl:SetVisible(false)
pnl:AddFunction("medialiblua", "Event", function(id, jsonstr)
self:handleHTMLEvent(id, util.JSONToTable(jsonstr))
end)
end
function HTMLMedia:getBaseService()
return "html"
end
function HTMLMedia:openUrl(url)
self.panel:OpenURL(url)
self.URLChanged = CurTime()
end
function HTMLMedia:runJS(js, ...)
local code = string.format(js, ...)
self.panel:QueueJavascript(code)
end
function HTMLMedia:handleHTMLEvent(id, event)
if medialib.DEBUG then
MsgN("[MediaLib] HTML Event: " .. id .. " (" .. table.ToString(event) .. ")")
end
if id == "stateChange" then
local state = event.state
local setToState
if event.time then
self.timeKeeper:seek(event.time)
end
if state == "playing" then
setToState = "playing"
self.timeKeeper:play()
elseif state == "ended" or state == "paused" or state == "buffering" then
setToState = state
self.timeKeeper:pause()
end
if setToState then
self.state = setToState
self:emit(setToState)
end
elseif id == "playerLoaded" then
for _,fn in pairs(self.commandQueue or {}) do
fn()
end
elseif id == "error" then
self:emit("error", {errorId = "service_error", errorName = "Error from service: " .. tostring(event.message)})
else
MsgN("[MediaLib] Unhandled HTML event " .. tostring(id))
end
end
function HTMLMedia:getState()
return self.state
end
local cvar_updatestride = CreateConVar("medialib_html_updatestride", "1", FCVAR_ARCHIVE)
function HTMLMedia:updateTexture()
local framenumber = FrameNumber()
local framesSinceUpdate = (framenumber - (self.lastUpdatedFrame or 0))
if framesSinceUpdate >= cvar_updatestride:GetInt() then
self.panel:UpdateHTMLTexture()
self.lastUpdatedFrame = framenumber
end
end
function HTMLMedia:getHTMLMaterial()
if self._htmlMat then
return self._htmlMat
end
local mat = self.panel:GetHTMLMaterial()
self._htmlMat = mat
return mat
end
function HTMLMedia:draw(x, y, w, h)
self:updateTexture()
local mat = self:getHTMLMaterial()
surface.SetMaterial(mat)
surface.SetDrawColor(255, 255, 255)
local w_frac, h_frac = panel_width / mat:Width(), panel_height / mat:Height()
surface.DrawTexturedRectUV(x or 0, y or 0, w or panel_width, h or panel_height, 0, 0, w_frac, h_frac)
end
function HTMLMedia:getTime()
return self.timeKeeper:getTime()
end
function HTMLMedia:setQuality(qual)
if self.lastSetQuality and self.lastSetQuality == qual then
return
end
self.lastSetQuality = qual
self:runJS("medialibDelegate.run('setQuality', {quality: %q})", qual)
end
-- This applies the volume to the HTML panel
-- There is a undocumented 'internalVolume' variable, that can be used by eg 3d vol
function HTMLMedia:applyVolume()
local ivol = self.internalVolume or 1
local rvol = self.volume or 1
local vol = ivol * rvol
if self.lastSetVolume and self.lastSetVolume == vol then
return
end
self.lastSetVolume = vol
self:runJS("medialibDelegate.run('setVolume', {vol: %f})", vol)
end
-- This sets a volume variable
function HTMLMedia:setVolume(vol)
self.volume = vol
self:applyVolume()
end
function HTMLMedia:getVolume()
-- could cookies potentially set the volume to something other than 1?
return self.volume or 1
end
-- if we dont rely on callbacks from JS, this will be the 'buffer' time for seeks
local SEEK_BUFFER = 0.2
function HTMLMedia:seek(time)
-- Youtube does not seem to properly callback with a 'seek' event (works in Chrome)
-- Workaround by setting timeseeker time instantly here with a small buffer
-- Using workaround is ok because if we somehow get event later, it'll correct
-- the time that was set wrongly here
self.timeKeeper:seek(time - SEEK_BUFFER)
self:runJS("medialibDelegate.run('seek', {time: %.1f})", time)
end
-- See HTMLService:hasReliablePlaybackEvents()
function HTMLMedia:hasReliablePlaybackEvents()
local service = self:getService()
return service and service:hasReliablePlaybackEvents(self)
end
function HTMLMedia:play()
if not self:hasReliablePlaybackEvents() then
self.timeKeeper:play()
end
self:runJS("medialibDelegate.run('play')")
end
function HTMLMedia:pause()
if not self:hasReliablePlaybackEvents() then
self.timeKeeper:pause()
end
self:runJS("medialibDelegate.run('pause')")
end
function HTMLMedia:stop()
AwesomiumPool.free(self.panel)
self.panel = nil
self.timeKeeper:pause()
self:emit("ended", {stopped = true})
self:emit("destroyed")
end
function HTMLMedia:runCommand(fn)
if self._playerLoaded then
fn()
else
self.commandQueue = self.commandQueue or {}
self.commandQueue[#self.commandQueue+1] = fn
end
end
function HTMLMedia:isValid()
return IsValid(self.panel)
end
end
-- 'service_bass'; CodeLen/MinifiedLen 6685/6685; Dependencies [oop,mediaregistry]
medialib.modulePlaceholder("service_bass")
do
local oop = medialib.load("oop")
local BASSService = oop.class("BASSService", "Service")
function BASSService:load(url, opts)
local media = oop.class("BASSMedia")()
self:loadMediaObject(media, url, opts)
return media
end
local BASSMedia = oop.class("BASSMedia", "Media")
function BASSMedia:initialize()
self.bassPlayOptions = {"noplay", "noblock"}
self.commandQueue = {}
end
function BASSMedia:getBaseService()
return "bass"
end
function BASSMedia:updateFFT()
local curFrame = FrameNumber()
if self._lastFFTUpdate and self._lastFFTUpdate == curFrame then return end
self._lastFFTUpdate = curFrame
local chan = self.chan
if not IsValid(chan) then return end
self.fftValues = self.fftValues or {}
chan:FFT(self.fftValues, FFT_512)
end
function BASSMedia:getFFT()
return self.fftValues
end
function BASSMedia:draw(x, y, w, h)
surface.SetDrawColor(0, 0, 0)
surface.DrawRect(x, y, w, h)
self:updateFFT()
local fftValues = self:getFFT()
if not fftValues then return end
local valCount = #fftValues
local valsPerX = (valCount == 0 and 1 or (w/valCount))
local barw = w / (valCount)
for i=1, valCount do
surface.SetDrawColor(HSVToColor(i, 0.9, 0.5))
local barh = fftValues[i]*h
surface.DrawRect(x + i*barw, y + (h-barh), barw, barh)
end
end
function BASSMedia:openUrl(url)
self._openingInfo = {"url", url}
local flags = table.concat(self.bassPlayOptions, " ")
sound.PlayURL(url, flags, function(chan, errId, errName)
self:bassCallback(chan, errId, errName)
end)
end
function BASSMedia:openFile(path)
self._openingInfo = {"file", path}
local flags = table.concat(self.bassPlayOptions, " ")
sound.PlayFile(path, flags, function(chan, errId, errName)
self:bassCallback(chan, errId, errName)
end)
end
-- Attempts to reload the stream
function BASSMedia:reload()
local type, resource = unpack(self._openingInfo or {})
if not type then
MsgN("[Medialib] Attempting to reload BASS stream that was never started the first time!")
return
end
-- stop existing channel if it exists
if IsValid(self.chan) then
self.chan:Stop()
self.chan = nil
end
-- Remove stop flag, clear cmd queue, stop state checker
self._stopped = false
self:stopStateChecker()
self.commandQueue = {}
MsgN("[Medialib] Attempting to reload BASS stream ", type, resource)
if type == "url" then
self:openUrl(resource)
elseif type == "file" then
self:openFile(resource)
elseif type then
MsgN("[Medialib] Failed to reload audio resource ", type, resource)
return
end
self:applyVolume(true)
if self._commandState == "play" then
self:play()
end
end
function BASSMedia:bassCallback(chan, errId, errName)
if not IsValid(chan) then
ErrorNoHalt("[MediaLib] BassMedia play failed: ", errName)
self._stopped = true
self:emit("error", "loading_failed", string.format("BASS error id: %s; name: %s", errId, errName))
return
end
-- Check if media was stopped before loading
if self._stopped then
MsgN("[MediaLib] Loading BASS media aborted; stop flag was enabled")
chan:Stop()
return
end
self.chan = chan
for _,c in pairs(self.commandQueue) do
c(chan)
end
-- Empty queue
self.commandQueue = {}
self:startStateChecker()
end
function BASSMedia:startStateChecker()
timer.Create("MediaLib_BASS_EndChecker_" .. self:hashCode(), 1, 0, function()
if IsValid(self.chan) and self.chan:GetState() == GMOD_CHANNEL_STOPPED then
self:emit("ended")
self:stopStateChecker()
end
end)
end
function BASSMedia:stopStateChecker()
timer.Remove("MediaLib_BASS_EndChecker_" .. self:hashCode())
end
function BASSMedia:runCommand(fn)
if IsValid(self.chan) then
fn(self.chan)
else
self.commandQueue[#self.commandQueue+1] = fn
end
end
-- This applies the volume to the HTML panel
-- There is a undocumented 'internalVolume' variable, that can be used by eg 3d vol
function BASSMedia:applyVolume(force)
local ivol = self.internalVolume or 1
local rvol = self.volume or 1
local vol = ivol * rvol
if not force and self.lastSetVolume and self.lastSetVolume == vol then
return
end
self.lastSetVolume = vol
self:runCommand(function(chan) chan:SetVolume(vol) end)
end
function BASSMedia:setVolume(vol)
self.volume = vol
self:applyVolume()
end
function BASSMedia:getVolume()
return self.volume or 1
end
function BASSMedia:seek(time)
self:runCommand(function(chan)
if chan:IsBlockStreamed() then return end
self._seekingTo = time
local timerId = "MediaLib_BASSMedia_Seeker_" .. self:hashCode()
local function AttemptSeek()
-- someone used :seek with other time
if self._seekingTo ~= time or
-- chan not valid
not IsValid(chan) then
timer.Destroy(timerId)
return
end
chan:SetTime(time)
-- seek succeeded
if math.abs(chan:GetTime() - time) < 0.25 then
timer.Destroy(timerId)
end
end
timer.Create(timerId, 0.2, 0, AttemptSeek)
AttemptSeek()
end)
end
function BASSMedia:getTime()
if self:isValid() and IsValid(self.chan) then
return self.chan:GetTime()
end
return 0
end
function BASSMedia:getState()
if not self:isValid() then return "error" end
if not IsValid(self.chan) then return "loading" end
local bassState = self.chan:GetState()
if bassState == GMOD_CHANNEL_PLAYING then return "playing" end
if bassState == GMOD_CHANNEL_PAUSED then return "paused" end
if bassState == GMOD_CHANNEL_STALLED then return "buffering" end
if bassState == GMOD_CHANNEL_STOPPED then return "paused" end -- umm??
return
end
function BASSMedia:play()
self:runCommand(function(chan)
chan:Play()
self:emit("playing")
self._commandState = "play"
end)
end
function BASSMedia:pause()
self:runCommand(function(chan)
chan:Pause()
self:emit("paused")
self._commandState = "pause"
end)
end
function BASSMedia:stop()
self._stopped = true
self:runCommand(function(chan)
chan:Stop()
self:emit("ended", {stopped = true})
self:emit("destroyed")
self:stopStateChecker()
end)
end
function BASSMedia:isValid()
return not self._stopped
end
local mediaregistry = medialib.load("mediaregistry")
local netmsgid = "ML_MapCleanHack_" .. medialib.VERSION
if CLIENT then
-- Logic for reloading BASS streams after map cleanups
-- Workaround until gmod issue #2874 gets fixed
net.Receive(netmsgid, function()
for _,v in pairs(mediaregistry.get()) do
-- BASS media that should play, yet does not
if v:getBaseService() == "bass" and v:isValid() and IsValid(v.chan) and v.chan:GetState() == GMOD_CHANNEL_STOPPED then
v:reload()
end
end
end)
end
if SERVER then
util.AddNetworkString(netmsgid)
hook.Add("PostCleanupMap", "MediaLib_BassReload" .. medialib.VERSION, function()
net.Start(netmsgid)
net.Broadcast()
end)
end
end
medialib.FolderItems["services/gdrive.lua"] = "local oop = medialib.load(\"oop\")\n\nlocal GDriveService = oop.class(\"GDriveService\", \"HTMLService\")\nGDriveService.identifier = \"GDrive\"\n\nlocal all_patterns = {\"^https?://drive.google.com/file/d/([^/]*)/edit\"}\n\nfunction GDriveService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id = string.match(url, pattern)\n\t\tif id then\n\t\t\treturn {id = id}\n\t\tend\n\tend\nend\n\nfunction GDriveService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nlocal function urlencode(str)\n if (str) then\n str = string.gsub (str, \"\\n\", \"\\r\\n\")\n str = string.gsub (str, \"([^%w ])\",\n function (c) return string.format (\"%%%02X\", string.byte(c)) end)\n str = string.gsub (str, \" \", \"+\")\n end\n return str \nend\n\nlocal player_url = \"https://wyozi.github.io/gmod-medialib/mp4.html?id=%s\"\nlocal gdrive_stream_url = \"https://drive.google.com/uc?export=download&confirm=yTib&id=%s\"\nfunction GDriveService:resolveUrl(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal playerUrl = string.format(player_url, urlencode(string.format(gdrive_stream_url, urlData.id)))\n\n\tcallback(playerUrl, {start = urlData.start})\nend\n\nfunction GDriveService:directQuery(url, callback)\n\tcallback(nil, {\n\t\ttitle = url:match(\"([^/]+)$\")\n\t})\nend\n\nfunction GDriveService:hasReliablePlaybackEvents(media)\n\treturn true\nend\n\nreturn GDriveService"
medialib.FolderItems["services/mp4.lua"] = "local oop = medialib.load(\"oop\")\n\nlocal Mp4Service = oop.class(\"Mp4Service\", \"HTMLService\")\nMp4Service.identifier = \"mp4\"\n\nlocal all_patterns = {\"^https?://.*%.mp4\"}\n\nfunction Mp4Service:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id = string.match(url, pattern)\n\t\tif id then\n\t\t\treturn {id = id}\n\t\tend\n\tend\nend\n\nfunction Mp4Service:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nlocal player_url = \"https://wyozi.github.io/gmod-medialib/mp4.html?id=%s\"\nfunction Mp4Service:resolveUrl(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal playerUrl = string.format(player_url, urlData.id)\n\n\tcallback(playerUrl, {start = urlData.start})\nend\n\nfunction Mp4Service:directQuery(url, callback)\n\tcallback(nil, {\n\t\ttitle = url:match(\"([^/]+)$\")\n\t})\nend\n\nfunction Mp4Service:hasReliablePlaybackEvents(media)\n\treturn true\nend\n\nreturn Mp4Service"
medialib.FolderItems["services/soundcloud.lua"] = "local oop = medialib.load(\"oop\")\n\nlocal SoundcloudService = oop.class(\"SoundcloudService\", \"BASSService\")\nSoundcloudService.identifier = \"soundcloud\"\n\nlocal all_patterns = {\n\t\"^https?://www.soundcloud.com/([A-Za-z0-9_%-]+/[A-Za-z0-9_%-]+)/?.*$\",\n\t\"^https?://soundcloud.com/([A-Za-z0-9_%-]+/[A-Za-z0-9_%-]+)/?.*$\",\n}\n\n-- Support url that passes track id directly\nlocal id_pattern = \"^https?://api.soundcloud.com/tracks/(%d+)\"\n\nfunction SoundcloudService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal path = string.match(url, pattern)\n\t\tif path then\n\t\t\treturn {path = path}\n\t\tend\n\tend\n\n\tlocal id = string.match(url, id_pattern)\n\tif id then\n\t\treturn {id = id}\n\tend\nend\n\nfunction SoundcloudService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nfunction SoundcloudService:resolveUrl(url, callback)\n\tlocal apiKey = medialib.SOUNDCLOUD_API_KEY\n\tif not apiKey then\n\t\tErrorNoHalt(\"SoundCloud error: Missing SoundCloud API key\")\n\t\treturn\n\tend\n\n\tlocal urlData = self:parseUrl(url)\n\n\tif urlData.id then\n\t\t-- id passed directly; nice, we can skip resolve.json\n\t\tcallback(string.format(\"https://api.soundcloud.com/tracks/%s/stream?client_id=%s\", urlData.id, apiKey), {})\n\telse\n\t\thttp.Fetch(\n\t\t\tstring.format(\"https://api.soundcloud.com/resolve.json?url=http://soundcloud.com/%s&client_id=%s\", urlData.path, apiKey),\n\t\t\tfunction(data)\n\t\t\t\tlocal jsonTable = util.JSONToTable(data)\n\t\t\t\tif not jsonTable then\n\t\t\t\t\tErrorNoHalt(\"Failed to retrieve SC track id for \" .. urlData.path .. \": empty JSON\")\n\t\t\t\t\treturn\n\t\t\t\tend\n\n\t\t\t\tlocal id = jsonTable.id\n\t\t\t\tcallback(string.format(\"https://api.soundcloud.com/tracks/%s/stream?client_id=%s\", id, apiKey), {})\n\t\t\tend)\n\tend\nend\n\nfunction SoundcloudService:directQuery(url, callback)\n\tlocal apiKey = medialib.SOUNDCLOUD_API_KEY\n\tif not apiKey then\n\t\tcallback(\"Missing SoundCloud API key\")\n\t\treturn\n\tend\n\n\tlocal urlData = self:parseUrl(url)\n\n\tlocal metaurl\n\tif urlData.path then\n\t\tmetaurl = string.format(\"https://api.soundcloud.com/resolve.json?url=http://soundcloud.com/%s&client_id=%s\", urlData.path, apiKey)\n\telse\n\t\tmetaurl = string.format(\"https://api.soundcloud.com/tracks/%s?client_id=%s\", urlData.id, apiKey)\n\tend\n\n\thttp.Fetch(metaurl, function(result, size)\n\t\tif size == 0 then\n\t\t\tcallback(\"http body size = 0\")\n\t\t\treturn\n\t\tend\n\n\t\tlocal entry = util.JSONToTable(result)\n\n\t\tif entry.errors then\n\t\t\tlocal msg = entry.errors[1].error_message or \"error\"\n\n\t\t\tlocal translated = msg\n\t\t\tif string.StartWith(msg, \"404\") then\n\t\t\t\ttranslated = \"Invalid id\"\n\t\t\tend\n\n\t\t\tcallback(translated)\n\t\t\treturn\n\t\tend\n\n\t\tcallback(nil, {\n\t\t\ttitle = entry.title,\n\t\t\tduration = tonumber(entry.duration) / 1000\n\t\t})\n\tend, function(err) callback(\"HTTP: \" .. err) end)\nend\n\nreturn SoundcloudService\n"
medialib.FolderItems["services/twitch.lua"] = "local oop = medialib.load(\"oop\")\n\nlocal TwitchService = oop.class(\"TwitchService\", \"HTMLService\")\nTwitchService.identifier = \"twitch\"\n\nlocal all_patterns = {\n\t\"https?://www.twitch.tv/([A-Za-z0-9_%-]+)\",\n\t\"https?://twitch.tv/([A-Za-z0-9_%-]+)\"\n}\n\nfunction TwitchService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id = string.match(url, pattern)\n\t\tif id then\n\t\t\treturn {id = id}\n\t\tend\n\tend\nend\n\nfunction TwitchService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nlocal player_url = \"https://wyozi.github.io/gmod-medialib/twitch.html?channel=%s\"\nfunction TwitchService:resolveUrl(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal playerUrl = string.format(player_url, urlData.id)\n\n\tcallback(playerUrl, {start = urlData.start})\nend\n\nfunction TwitchService:directQuery(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal metaurl = string.format(\"https://api.twitch.tv/kraken/channels/%s\", urlData.id)\n\n\thttp.Fetch(metaurl, function(result, size)\n\t\tif size == 0 then\n\t\t\tcallback(\"http body size = 0\")\n\t\t\treturn\n\t\tend\n\n\t\tlocal data = {}\n\t\tdata.id = urlData.id\n\n\t\tlocal jsontbl = util.JSONToTable(result)\n\n\t\tif jsontbl then\n\t\t\tif jsontbl.error then\n\t\t\t\tcallback(jsontbl.message)\n\t\t\t\treturn\n\t\t\telse\n\t\t\t\tdata.title = jsontbl.display_name .. \": \" .. jsontbl.status\n\t\t\tend\n\t\telse\n\t\t\tdata.title = \"ERROR\"\n\t\tend\n\n\t\tcallback(nil, data)\n\tend, function(err) callback(\"HTTP: \" .. err) end)\nend\n\nreturn TwitchService"
medialib.FolderItems["services/vimeo.lua"] = "local oop = medialib.load(\"oop\")\n\nlocal VimeoService = oop.class(\"VimeoService\", \"HTMLService\")\nVimeoService.identifier = \"vimeo\"\n\nlocal all_patterns = {\n\t\"https?://www.vimeo.com/([0-9]+)\",\n\t\"https?://vimeo.com/([0-9]+)\",\n\t\"https?://www.vimeo.com/channels/staffpicks/([0-9]+)\",\n\t\"https?://vimeo.com/channels/staffpicks/([0-9]+)\",\n}\n\nfunction VimeoService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id = string.match(url, pattern)\n\t\tif id then\n\t\t\treturn {id = id}\n\t\tend\n\tend\nend\n\nfunction VimeoService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nlocal player_url = \"http://wyozi.github.io/gmod-medialib/vimeo.html?id=%s\"\nfunction VimeoService:resolveUrl(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal playerUrl = string.format(player_url, urlData.id)\n\n\tcallback(playerUrl, {start = urlData.start})\nend\n\nfunction VimeoService:directQuery(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal metaurl = string.format(\"http://vimeo.com/api/v2/video/%s.json\", urlData.id)\n\n\thttp.Fetch(metaurl, function(result, size, headers, httpcode)\n\t\tif size == 0 then\n\t\t\tcallback(\"http body size = 0\")\n\t\t\treturn\n\t\tend\n\n\t\tif httpcode == 404 then\n\t\t\tcallback(\"Invalid id\")\n\t\t\treturn\n\t\tend\n\n\t\tlocal data = {}\n\t\tdata.id = urlData.id\n\n\t\tlocal jsontbl = util.JSONToTable(result)\n\n\t\tif jsontbl then\n\t\t\tdata.title = jsontbl[1].title\n\t\t\tdata.duration = jsontbl[1].duration\n\t\telse\n\t\t\tdata.title = \"ERROR\"\n\t\tend\n\n\t\tcallback(nil, data)\n\tend, function(err) callback(\"HTTP: \" .. err) end)\nend\n\nfunction VimeoService:hasReliablePlaybackEvents(media)\n\treturn true\nend\n\nreturn VimeoService\n"
medialib.FolderItems["services/webaudio.lua"] = "local oop = medialib.load(\"oop\")\nlocal WebAudioService = oop.class(\"WebAudioService\", \"BASSService\")\nWebAudioService.identifier = \"webaudio\"\n\nlocal all_patterns = {\n\t\"^https?://(.*)%.mp3\",\n\t\"^https?://(.*)%.ogg\",\n}\n\nfunction WebAudioService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id = string.match(url, pattern)\n\t\tif id then\n\t\t\treturn {id = id}\n\t\tend\n\tend\nend\n\nfunction WebAudioService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nfunction WebAudioService:resolveUrl(url, callback)\n\tcallback(url, {})\nend\n\nfunction WebAudioService:directQuery(url, callback)\n\tcallback(nil, {\n\t\ttitle = url:match(\"([^/]+)$\")\n\t})\nend\n\nreturn WebAudioService"
medialib.FolderItems["services/webm.lua"] = "local oop = medialib.load(\"oop\")\n\nlocal WebmService = oop.class(\"WebmService\", \"HTMLService\")\nWebmService.identifier = \"webm\"\n\nlocal all_patterns = {\"^https?://.*%.webm\"}\n\nfunction WebmService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id = string.match(url, pattern)\n\t\tif id then\n\t\t\treturn {id = id}\n\t\tend\n\tend\nend\n\nfunction WebmService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nlocal player_url = \"http://wyozi.github.io/gmod-medialib/webm.html?id=%s\"\nfunction WebmService:resolveUrl(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal playerUrl = string.format(player_url, urlData.id)\n\n\tcallback(playerUrl, {start = urlData.start})\nend\n\nfunction WebmService:directQuery(url, callback)\n\tcallback(nil, {\n\t\ttitle = url:match(\"([^/]+)$\")\n\t})\nend\n\nreturn WebmService"
medialib.FolderItems["services/webradio.lua"] = "local oop = medialib.load(\"oop\")\nlocal WebRadioService = oop.class(\"WebRadioService\", \"BASSService\")\nWebRadioService.identifier = \"webradio\"\n\nlocal all_patterns = {\n\t\"^https?://(.*)%.pls\",\n\t\"^https?://(.*)%.m3u\"\n}\n\nfunction WebRadioService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id = string.match(url, pattern)\n\t\tif id then\n\t\t\treturn {id = id}\n\t\tend\n\tend\nend\n\nfunction WebRadioService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nfunction WebRadioService:resolveUrl(url, callback)\n\tcallback(url, {})\nend\n\nfunction WebRadioService:directQuery(url, callback)\n\tcallback(nil, {\n\t\ttitle = url:match(\"([^/]+)$\") -- the filename is the best we can get (unless we parse pls?)\n\t})\nend\n\nreturn WebRadioService"
medialib.FolderItems["services/youtube.lua"] = "local oop = medialib.load(\"oop\")\n\nlocal YoutubeService = oop.class(\"YoutubeService\", \"HTMLService\")\nYoutubeService.identifier = \"youtube\"\n\nlocal raw_patterns = {\n\t\"^https?://[A-Za-z0-9%.%-]*%.?youtu%.be/([A-Za-z0-9_%-]+)\",\n\t\"^https?://[A-Za-z0-9%.%-]*%.?youtube%.com/watch%?.*v=([A-Za-z0-9_%-]+)\",\n\t\"^https?://[A-Za-z0-9%.%-]*%.?youtube%.com/v/([A-Za-z0-9_%-]+)\",\n}\nlocal all_patterns = {}\n\n-- Appends time modifier patterns to each pattern\nfor k,p in pairs(raw_patterns) do\n\tlocal function with_sep(sep)\n\t\ttable.insert(all_patterns, p .. sep .. \"t=(%d+)m(%d+)s\")\n\t\ttable.insert(all_patterns, p .. sep .. \"t=(%d+)s?\")\n\tend\n\n\t-- We probably support more separators than youtube itself, but that does not matter\n\twith_sep(\"#\")\n\twith_sep(\"&\")\n\twith_sep(\"?\")\n\n\ttable.insert(all_patterns, p)\nend\n\nfunction YoutubeService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id, time1, time2 = string.match(url, pattern)\n\t\tif id then\n\t\t\tlocal time_sec = 0\n\t\t\tif time1 and time2 then\n\t\t\t\ttime_sec = tonumber(time1)*60 + tonumber(time2)\n\t\t\telse\n\t\t\t\ttime_sec = tonumber(time1)\n\t\t\tend\n\n\t\t\treturn {\n\t\t\t\tid = id,\n\t\t\t\tstart = time_sec\n\t\t\t}\n\t\tend\n\tend\nend\n\nfunction YoutubeService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nlocal player_url = \"https://wyozi.github.io/gmod-medialib/youtube.html?id=%s\"\nfunction YoutubeService:resolveUrl(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal playerUrl = string.format(player_url, urlData.id)\n\n\tcallback(playerUrl, {start = urlData.start})\nend\n\n-- http://en.wikipedia.org/wiki/ISO_8601#Durations\n-- Cheers wiox :))\nlocal function PTToSeconds(str)\n\tlocal h = str:match(\"(%d+)H\") or 0\n\tlocal m = str:match(\"(%d+)M\") or 0\n\tlocal s = str:match(\"(%d+)S\") or 0\n\treturn h*(60*60) + m*60 + s\nend\n\nlocal API_KEY = \"AIzaSyBmQHvMSiOTrmBKJ0FFJ2LmNtc4YHyUJaQ\"\nfunction YoutubeService:directQuery(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal metaurl = string.format(\"https://www.googleapis.com/youtube/v3/videos?part=snippet%%2CcontentDetails&id=%s&key=%s\", urlData.id, API_KEY)\n\n\thttp.Fetch(metaurl, function(result, size)\n\t\tif size == 0 then\n\t\t\tcallback(\"http body size = 0\")\n\t\t\treturn\n\t\tend\n\n\t\tlocal data = {}\n\t\tdata.id = urlData.id\n\n\t\tlocal jsontbl = util.JSONToTable(result)\n\n\t\tif jsontbl and jsontbl.items then\n\t\t\tlocal item = jsontbl.items[1]\n\t\t\tif not item then\n\t\t\t\tcallback(\"No video id found\")\n\t\t\t\treturn\n\t\t\tend\n\n\t\t\tdata.title = item.snippet.title\n\t\t\tdata.duration = tonumber(PTToSeconds(item.contentDetails.duration))\n\t\telse\n\t\t\tcallback(result)\n\t\t\treturn\n\t\tend\n\n\t\tcallback(nil, data)\n\tend, function(err) callback(\"HTTP: \" .. err) end)\nend\n\nfunction YoutubeService:hasReliablePlaybackEvents(media)\n\treturn true\nend\n\nreturn YoutubeService\n"
-- 'serviceloader'; CodeLen/MinifiedLen 533/533; Dependencies [servicebase,service_html,service_bass,media,oop]
medialib.modulePlaceholder("serviceloader")
do
medialib.load("servicebase")
medialib.load("service_html")
medialib.load("service_bass")
local media = medialib.load("media")
-- Load the actual service files
for _,file in medialib.folderIterator("services") do
if medialib.DEBUG then
print("[MediaLib] Registering service " .. file.name)
end
if SERVER then file:addcs() end
local status, err = pcall(function() return file:load() end)
if status then
media.registerService(err.identifier, err)
else
print("[MediaLib] Failed to load service ", file, ": ", err)
end
end
end
-- '__loader'; CodeLen/MinifiedLen 325/325; Dependencies [mediabase,media,serviceloader]
medialib.modulePlaceholder("__loader")
do
-- This file loads required modules in the correct order.
-- For development version: this file is automatically called after autorun/medialib.lua
-- For distributable: this file is loaded after packed modules have been added to medialib
medialib.load("mediabase")
medialib.load("media")
medialib.load("serviceloader")
end
return medialib