Skip to content

Instantly share code, notes, and snippets.

@Cyberes
Last active February 14, 2023 23:01
Show Gist options
  • Save Cyberes/4ee247bf78b38aa1073bc5b58c5afdd9 to your computer and use it in GitHub Desktop.
Save Cyberes/4ee247bf78b38aa1073bc5b58c5afdd9 to your computer and use it in GitHub Desktop.
Fix Matrix Synapse not allowing video seeking (nginx)
# Fix video buffering
# https://github.com/matrix-org/synapse/issues/4780#issuecomment-1003101558
# luarocks install pgmoon
set $our_homeserver "matrix.example.com"; # Set this to the hostname of your server
set $media_store "/var/lib/matrix-synapse/media"; # no trailing slash
set $media_status_header true; # add a header to show the status of the media.
location ~* "^/_matrix/media/r0/download/(.*?)/([A-Za-z0-9]{2})([A-Za-z0-9]{2})(.*)$" {
# Enable video seeking.
add_header Accept-Ranges bytes;
# Emulate the headers.
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, HEAD, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'X-Requested-With, Content-Type, Authorization, Date, Range' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
add_header 'Cross-Origin-Resource-Policy' 'cross-origin' always;
add_header 'Content-Security-Policy' "sandbox; default-src 'none'; script-src 'none'; plugin-types application/pdf; style-src 'unsafe-inline'; media-src 'self'; object-src 'self';" always;
add_header 'Content-Security-Policy' 'sandbox' always;
add_header 'Referrer-Policy' 'no-referrer' always;
add_header 'X-Frame-Options' 'DENY' always;
add_header 'X-Content-Type-Options' 'nosniff' always;
# Set variables to use in lua.
set $media_host $1;
set $part_a $2;
set $part_b $3;
set $part_c $4;
# Need to figure out where the file will be located.
set_by_lua_block $file_location {
-- Determine if our homeserver is on a subdomain so that we can serve local files mapped to the root domain too.
-- This should work if you have something wierd like matrix.sub.example.com
_, _, our_root_domain = string.find(ngx.var.our_homeserver, ".*[.](.*[.].*)")
if ngx.var.media_host == ngx.var.our_homeserver or our_root_domain == ngx.var.media_host then
return ngx.var.media_store .. '/local_content'
else
return ngx.var.media_store .. '/remote_content/' .. ngx.var.media_host
end
}
# Get the correct file type to use.
# access_by_lua_block is a workaround for cosocket limitations.
access_by_lua_block {
local pgmoon = require("pgmoon")
local http = require("resty.http")
local media_status_header_set = false
function media_status_header(status)
if ngx.var.media_status_header == "true" and not media_status_header_set then
ngx.header["Synapse-Remote-Media-Status"] = status
media_status_header_set = true
end
end
local pg = pgmoon.new({
host = "127.0.0.1",
port = "5432",
database = "synapse",
user = "synapse_user",
password = "klmdkjlsadlkj"
})
assert(pg:connect())
local esc = pg:escape_literal(ngx.var.part_a .. ngx.var.part_b .. ngx.var.part_c)
success = false
-- Add a custom header to let you know it was served through nginx instead of Synapse.
if ngx.var.media_status_header == "true" then
ngx.header["Synapse-Media-Server"] = "nginx"
end
-- Check both the remote and local file store for our file ID.
-- The column filesystem_id in remote_media_cache should get mapped to media_id.
local sql = "(SELECT media_type, upload_name, media_id FROM local_media_repository WHERE media_id = " .. esc .. ") UNION ALL (SELECT media_type, upload_name, filesystem_id FROM remote_media_cache WHERE media_id = " .. esc .. ")"
res = assert(pg:query(sql))
-- If the file doesnt exist, ask Synapse to fetch it for us and wait a max of 10s for it to finish.
i = 0
if res == nil or res[1] == nil then
local http_res, http_err = http.new():request_uri("http://127.0.0.1:8008/_matrix/media/r0/download/" .. ngx.var.media_host .. "/" .. ngx.var.part_a .. ngx.var.part_b .. ngx.var.part_c, { method = "GET" })
media_status_header(http_res.status)
res = assert(pg:query(sql))
end
if res ~= nil and res[1] ~= nil then
res = res[1]
-- Check that the data is present actually.
if res["media_type"] ~= nil and res["media_type"] ~= "" and res['media_id'] ~= nil and res['media_id'] ~= "" then
-- Update the parts of the file ID since it may have changed if it was a remote file.
_, _, ngx.var.part_a, ngx.var.part_b, ngx.var.part_c = string.find(res['media_id'], "(%w%w)(%w%w)(.*)$")
-- Sometimes internal/backend services wont populate the fields.
if res["upload_name"] ~= nil and res["upload_name"] ~= "" then
ngx.header["Content-Disposition"] = 'inline; filename=' .. res["upload_name"]
end
ngx.header["Content-Type"] = res["media_type"]
media_status_header("local")
success = true
else
media_status_header("data-error")
end
else
media_status_header("entry-not-found")
end
if not success then
-- If it doesnt exist on our disk then we will tell the browsers and CDN not to cache the page.
ngx.header["Cache-Control"] = "no-store, must-revalidate"
ngx.header["Pragma"] = "no-cache"
ngx.header["Expires"] = 0
else
ngx.header["Cache-Control"] = "public, max-age=86400" -- cache for 1 day
end
pg:keepalive()
pg = nil
}
alias $file_location/;
try_files $part_a/$part_b/$part_c =404;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment