do
	-- Note: build file expects these exact lines for them to be automatically replaced, so please don't change anything
	local VERSION = "local"
	local DISTRIBUTABLE = false

	medialib = medialib or {}

	medialib.VERSION = VERSION
	medialib.DISTRIBUTABLE = DISTRIBUTABLE
	medialib.INSTANCE = medialib.VERSION .. "_" .. tostring(10000 + math.random(90000))

	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

		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(_, 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 2939/2939; 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 _,metaName in pairs(metamethods) do
				cls_instance_meta[metaName] = function(...)
					local method = cls.members[metaName]
					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 4320/4320; 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 1246/1246; 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)
	hook.Add("MediaLib_ListAll", "MediaLib_" .. medialib.INSTANCE, function()
		print("Media for medialib version " .. medialib.INSTANCE .. ":")
		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.INSTANCE, 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.INSTANCE, 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 or {}
		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() 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() end

	-- Sub-services should override this
	function Service:directQuery() 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(chainedErr, chainedData)
			self:run(chainedErr, chainedData)
		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() 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 1015/1015; 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 8599/8599; 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 HTMLPool = {instances = {}}
	local function GetMaxPoolInstances()
		return medialib.MAX_HTMLPOOL_INSTANCES or 0
	end

	hook.Add("MediaLib_HTMLPoolInfo", medialib.INSTANCE, function()
		print(medialib.INSTANCE .. "> Free HTMLPool instance count: " .. #HTMLPool.instances .. "/" .. GetMaxPoolInstances())
	end)
	concommand.Add("medialib_htmlpoolinfo", function()
		hook.Run("MediaLib_HTMLPoolInfo")
	end)

	-- Automatic periodic cleanup of html pool objects
	timer.Create("MediaLib." .. medialib.INSTANCE .. ".HTMLPoolCleaner", 60, 0, function()
		if #HTMLPool.instances == 0 then return end

		local inst = table.remove(HTMLPool.instances, 1)
		if IsValid(inst) then inst:Remove() end
	end)
	function HTMLPool.newInstance()
		return vgui.Create("DHTML")
	end
	function HTMLPool.get()
		if #HTMLPool.instances == 0 then
			if medialib.DEBUG then
				MsgN("[MediaLib] Returning new instance; htmlpool empty")
			end
			return HTMLPool.newInstance()
		end

		local inst = table.remove(HTMLPool.instances, 1)
		if not IsValid(inst) then
			if medialib.DEBUG then
				MsgN("[MediaLib] Returning new instance; instance was invalid")
			end
			return HTMLPool.newInstance()
		end
		if medialib.DEBUG then
			MsgN("[MediaLib] Returning an instance from the HTML pool")
		end
		return inst
	end
	function HTMLPool.free(inst)
		if not IsValid(inst) then return end

		inst:Remove() -- Always remove old instances, why keep them?
	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 = HTMLPool.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
				if string.find(msg, "Mixed Content:", 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:setUpdateStrideOverride(override)
		self._updateStrideOverride = override
	end

	function HTMLMedia:updateTexture()
		local framenumber = FrameNumber()

		local nextTextureUpdateFrame = self._nextTextureUpdateFrame or 0

		local stride = self._updateStrideOverride or cvar_updatestride:GetInt()
		if nextTextureUpdateFrame <= framenumber then
			self.panel:UpdateHTMLTexture()
			self._nextTextureUpdateFrame = framenumber + stride
		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()

		-- [June 2017] CEF GetHTMLMaterial returns nil for some time after panel creation
		if not mat then
			return
		end

		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()
		HTMLPool.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 6642/6642; 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 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.INSTANCE
	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.INSTANCE, function()
			net.Start(netmsgid)
			net.Broadcast()
		end)
	end
end

-- '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