Last active
February 14, 2023 23:01
-
-
Save Cyberes/4ee247bf78b38aa1073bc5b58c5afdd9 to your computer and use it in GitHub Desktop.
Fix Matrix Synapse not allowing video seeking (nginx)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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