I am currently working on my website that supports file uploading. Part of this feature involves the ability to stream video from my site using the video tag in html. Currently I have this feature working flawlessly on desktop browsers such as chrome, but the second I try to stream a video on mobile, it simply doesn't load at all.
I have uploaded a sample video, and encourage anyone willing to help me to test it out themselves to see what im dealing with: https://upload.hellin.club/view-file.php?uuid=9034e9c1-1615-11f0-b42f-801844e26260&name=file_example_MP4_480_1_5MG.mp4
If anyone could help me fix this issue, id be very grateful!
Also here is my PHP script for viewing a file (which includes streaming a video):
<?php
include_once "includes/sys.php";
set_session_params();
session_start();
include_once "includes/getter.php";
$uuid = get_or_else("uuid");
$iframe = !isset($_GET["iframe"]) || $_GET["iframe"] == "true";
$url = get_url(sub_domain());
if ($uuid == null) {
header("location: " . $url . "search-files.php");
return;
}
include_once "includes/general-db-queries.php";
$upload = get_upload($uuid);
if ($upload == null) {
header("location: " . $url . "search-files.php");
return;
}
if ($upload->require_logged_in()) {
include_once "includes/verify-request.php";
if (!verify_request(false)) {
return;
}
include_once "includes/user.php";
$user = get_user();
$available_uploads = get_available_uploads($user);
$all_uploads = array_merge(...array_values($available_uploads));
if (empty(array_filter($all_uploads, fn($obj) => $obj->uuid === $upload->uuid))) {
header("location: " . $url . "search-files.php");
return;
}
}
include_once "includes/sanitize.php";
$path = $upload->file_path;
if (!file_exists($path)) {
http_response_code(404);
die("File not found");
}
$mime_type = $upload->file_type;
if (!$mime_type) {
$mime_type = mime_content_type($path) ?: "application/octet-stream";
}
$file_name = sanitize($upload->file_name);
$is_image = $upload->is_image();
$is_text = $upload->is_text();
$viewable = $is_text || $is_image || $upload->is_audio() || $upload->is_video();
$serve_as_iframe = $iframe && $viewable;
if (!$serve_as_iframe) {
while (ob_get_level()) ob_end_clean();
$is_encrypted = $upload->encryption_key !== null;
$file_contents = null;
$file_size = filesize($upload->file_path);
if ($is_encrypted) {
$file_contents = $upload->get_raw_contents();
$file_size = strlen($file_contents);
}
header('Content-Description: File Transfer');
header('Content-Type: ' . ($is_text ? "text/plain" : $mime_type));
header('Content-Disposition: inline; filename="' . $file_name . '"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . $file_size);
header("Access-Control-Allow-Origin: $url");
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: GET');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
header('Access-Control-Max-Age: 0');
header("Content-Security-Policy: script-src 'none'; object-src 'none'; frame-src 'self' $url;");
$chunk_size = 102400;
if (isset($_SERVER['HTTP_RANGE']) && !$is_encrypted) {
list($unit, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if ($unit == 'bytes') {
list($range_start, $range_end) = explode('-', $range);
$range_start = intval($range_start);
$range_end = $range_end ? intval($range_end) : $file_size - 1;
header('HTTP/1.1 206 Partial Content');
header("Content-Range: bytes $range_start-$range_end/$file_size");
header('Content-Length: ' . ($range_end - $range_start + 1));
$handle = fopen($path, 'rb');
fseek($handle, $range_start);
while (!feof($handle) && ftell($handle) <= $range_end) {
echo fread($handle, $chunk_size);
flush();
}
fclose($handle);
exit;
}
}
if ($upload->is_image() || $upload->is_text()) {
echo $upload->get_raw_contents();
} else if ($is_encrypted) {
$offset = 0;
while ($offset < $file_size) {
echo substr($file_contents, $offset, $chunk_size);
flush();
$offset += $chunk_size;
}
} else {
$handle = fopen($path, 'rb');
if (!$handle) {
http_response_code(500);
die("Failed to open file.");
}
while (!feof($handle)) {
echo fread($handle, $chunk_size);
flush();
}
fclose($handle);
}
if ($upload->one_use) {
$upload->delete();
} else {
$upload->increment_download_count();
}
return;
}
$stylesheet_version = filemtime("assets/style.css");
echo "<head>";
echo "<link rel='stylesheet' type='text/css' href='assets/style.css?v=$stylesheet_version' />";
echo "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
include_once "includes/config-reader.php";
$website_name = get_website_name();
$name = $upload->file_name;
echo "<title>$website_name - $name</title>";
echo "</head>";
echo "<body class='user-page'>";
echo "<script src='assets/scripts/theme.js'></script>";
include "includes/header.php";
$group = $upload->upload_group_uuid ?? "00000000-00000000-00000000-00000000";
echo "<a class='button' id='back-button' href='" . $url . "search-files.php?group=$group'>Back</a>";
echo "<div class='content flex-col flex-gap'>";
if ($is_image) {
echo "<img src='view-file.php?uuid=$uuid&iframe=false' class='view-img'/>";
} else if ($upload->is_video()) {
echo "<video controls='' preload='metadata' autoplay='' class='video-player' data-uuid='$uuid' name='media'><source src='view-file.php?uuid=$uuid&iframe=false' type='$mime_type'/>Your browser does not support this.</video>";
} else {
echo "<iframe src='view-file.php?uuid=$uuid&iframe=false' class='full-screen' sandbox='allow-scripts'></iframe>";
}
echo "</div>";
echo "</body>";
if ($upload->one_use) {
$upload->delete();
} else {
$upload->increment_download_count();
}
?>
Content-DescriptionandContent-Disposition: inlinemight not be helpful, when you want to stream the file.<video>tag in browsers: caniuse.com/video It should be fine, although Safari has some minor restrictions.'Content-Length: ' . $file_sizefirst, and then'Content-Length: ' . ($range_end - $range_start + 1)after - clients might not like two conflicting Content-Length headers. Either restructure this to only send the header when you know what the actual length will be; or try to set the second parameter ofheaderto true, so that it will replace a previously set header of the same name.autoplaywithoutmutedmight not work