"The Weird Case of Streaming in Safari" from Tomer Steinfeld explains the "rules" when it comes to streaming from a Safari client.
The server must:
Support 206 partial content, as shown in "Streaming video in Safari: Why is it so difficult?" from Ashley Davis: The status code varies depending on whether this is a request for the full file or a range request for a portion of the file. If it is a range request, the status code will be 206 (for partial content); otherwise, we use the regular old success status code of 200.
Return the correct Content-Type
header and type attribute
(Apparently, Safari checks three things in order to determine the type of resource to be a video. The type attribute
must match the Content-Type
header, and the URL must end with a matching extension.),
Add a matching file extension to the video src
URL.
Your node app, served by PM2, could look like:
const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();
app.get('/video/:file.mp4', (req, res) => {
const filePath = path.join(__dirname, 'videos', req.params.file + '.mp4');
const stat = fs.statSync(filePath);
const fileSize = stat.size;
const range = req.headers.range;
if (range) {
const parts = range.replace(/bytes=/, "").split("-");
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
const chunksize = (end - start) + 1;
const file = fs.createReadStream(filePath, { start, end });
const head = {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
'Content-Type': 'video/mp4'
};
res.writeHead(206, head);
file.pipe(res);
} else {
const head = {
'Content-Length': fileSize,
'Content-Type': 'video/mp4'
};
res.writeHead(200, head);
fs.createReadStream(filePath).pipe(res);
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
With the following modifications:
- The URL pattern has been changed to
/video/:file.mp4
, where :file
is the name of the video file without the extension. That would make sure that the URL ends with the .mp4
extension, satisfying Safari's requirement.
- The
Content-Type
header is set to 'video/mp4'
, matching the file extension and MIME type for MP4 videos.
- The server responds to Range header requests with a 206 status code, providing the necessary support for partial content.
That code should comply with Safari's requirements for streaming video content, and it should work for other browsers as well. Make sure that the videos are located in a directory named 'videos
' within your server's directory. You can, of course, modify the path as needed to match your actual file locations.