parse-server doesn't seem to support streaming in Safari / iOS, and the solution is to enable it using express and GridStore as follows:
syntax-server-example \ node_modules \ parsing-server \ Lib \ Routers \ FilesRouter
{ key: 'getHandler', value: function getHandler(req, res, content) { var config = new _Config2.default(req.params.appId); var filesController = config.filesController; var filename = req.params.filename; var video = '.mp4' var lastFourCharacters = video.substr(video.length - 4); if (lastFourCharacters == '.mp4') { filesController.handleVideoStream(req, res, filename).then(function (data) { }).catch(function (err) { console.log('404FilesRouter'); res.status(404); res.set('Content-Type', 'text/plain'); res.end('File not found.'); }); }else{ filesController.getFileData(config, filename).then(function (data) { res.status(200); res.end(data); }).catch(function (err) { res.status(404); res.set('Content-Type', 'text/plain'); res.end('File not found.'); }); } } } , ...
syntax-server-example \ node_modules \ parsing-server \ Lib \ Controllers \ FilesController
_createClass(FilesController, [{ key: 'getFileData', value: function getFileData(config, filename) { return this.adapter.getFileData(filename); } },{ key: 'handleVideoStream', value: function handleVideoStream(req, res, filename) { return this.adapter.handleVideoStream(req, res, filename); } }, ...
syntax-server example \ node_modules \ parsing server \ Lib \ Adapters \ Files \ GridStoreAdapter
... , { key: 'handleVideoStream', value: function handleVideoStream(req, res, filename) { return this._connect().then(function (database) { return _mongodb.GridStore.exist(database, filename).then(function () { var gridStore = new _mongodb.GridStore(database, filename, 'r'); gridStore.open(function(err, GridFile) { if(!GridFile) { res.send(404,'Not Found'); return; } console.log('filename'); StreamGridFile(GridFile, req, res); }); }); }) } }, ...
GridStore adapter bottom
function StreamGridFile(GridFile, req, res) { var buffer_size = 1024 * 1024;//1024Kb if (req.get('Range') != null) { //was: if(req.headers['range']) // Range request, partialle stream the file console.log('Range Request'); var parts = req.get('Range').replace(/bytes=/, "").split("-"); var partialstart = parts[0]; var partialend = parts[1]; var start = partialstart ? parseInt(partialstart, 10) : 0; var end = partialend ? parseInt(partialend, 10) : GridFile.length - 1; var chunksize = (end - start) + 1; if(chunksize == 1){ start = 0; partialend = false; } if(!partialend){ if(((GridFile.length-1) - start) < (buffer_size) ){ end = GridFile.length - 1; }else{ end = start + (buffer_size); } chunksize = (end - start) + 1; } if(start == 0 && end == 2){ chunksize = 1; } res.writeHead(206, { 'Cache-Control': 'no-cache', 'Content-Range': 'bytes ' + start + '-' + end + '/' + GridFile.length, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4', }); GridFile.seek(start, function () { // get GridFile stream var stream = GridFile.stream(true); var ended = false; var bufferIdx = 0; var bufferAvail = 0; var range = (end - start) + 1; var totalbyteswanted = (end - start) + 1; var totalbyteswritten = 0; // write to response stream.on('data', function (buff) { bufferAvail += buff.length; //Ok check if we have enough to cover our range if(bufferAvail < range) { //Not enough bytes to satisfy our full range if(bufferAvail > 0) { //Write full buffer res.write(buff); totalbyteswritten += buff.length; range -= buff.length; bufferIdx += buff.length; bufferAvail -= buff.length; } } else{ //Enough bytes to satisfy our full range! if(bufferAvail > 0) { var buffer = buff.slice(0,range); res.write(buffer); totalbyteswritten += buffer.length; bufferIdx += range; bufferAvail -= range; } } if(totalbyteswritten >= totalbyteswanted) { // totalbytes = 0; GridFile.close(); res.end(); this.destroy(); } }); }); }else{ // res.end(GridFile); // stream back whole file res.header('Cache-Control', 'no-cache'); res.header('Connection', 'keep-alive'); res.header("Accept-Ranges", "bytes"); res.header('Content-Type', 'video/mp4'); res.header('Content-Length', GridFile.length); var stream = GridFile.stream(true).pipe(res); } };
PS The original answer is given here @Bragegs - https://github.com/ParsePlatform/parse-server/issues/1440#issuecomment-212815625 . @ Stav1 also mentioned this in this thread, but unfortunately was it blocked?