Skip to content

Commit

Permalink
Use HTTP range requests (responses) to serve mp4 from assets
Browse files Browse the repository at this point in the history
Summary:
This PR solves a problem when video assets are used from third-party React Native components (e.g. [react-native-video](https://github.com/brentvatne/react-native-video). The video will not work while the assets are served from the react native packager because the used video component (iOS) relies on HTTP range requests.

I added a small fix that allows ranged requests (e.g. mp4) to be served in ranges.

To test this:

1. make new react native project
1. add [react-native-video](https://github.com/brentvatne/react-native-video) to xcode project
1. add video component to your project
```
import Video from 'react-native-video';
var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource');
/* ... /*
render() {
    let source = resolveAssetSource(require('./someVideoFile.mp4')) || {};
    return <Video /*....*/ source={source} />;
}
```

That should not work (if video is smaller than a few megabytes, open app a few times). Then add my fix, that should do the trick.
Closes #8219

Reviewed By: davidaurelio

Differential Revision: D3542485

Pulled By: frantic

fbshipit-source-id: e4f2e4d3aaafa8445e965259bf04ad107dba8a4f
  • Loading branch information
mroswald authored and Facebook Github Bot 5 committed Jul 13, 2016
1 parent 262397d commit 91ff686
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 2 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@
"xmldoc": "^0.4.0",
"yargs": "^3.24.0",
"yeoman-environment": "~1.2.7",
"yeoman-generator": "^0.20.3"
"yeoman-generator": "^0.20.3",
"mime-types": "2.1.11"
},
"devDependencies": {
"babel-eslint": "^6.0.0",
Expand Down
13 changes: 13 additions & 0 deletions packager/react-packager/src/Server/__tests__/Server-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,19 @@ describe('processRequest', () => {
expect(AssetServer.prototype.get).toBeCalledWith('imgs/a.png', 'ios');
expect(res.end).toBeCalledWith('i am image');
});

it('should serve range request', () => {
const req = {url: '/assets/imgs/a.png?platform=ios', headers: {range: 'bytes=0-3'}};
const res = {end: jest.fn(), writeHead: jest.fn()};
const mockData = 'i am image';

AssetServer.prototype.get.mockImpl(() => Promise.resolve(mockData));

server.processRequest(req, res);
jest.runAllTimers();
expect(AssetServer.prototype.get).toBeCalledWith('imgs/a.png', 'ios');
expect(res.end).toBeCalledWith(mockData.slice(0, 3));
});
});

describe('buildbundle(options)', () => {
Expand Down
23 changes: 22 additions & 1 deletion packager/react-packager/src/Server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const SourceMapConsumer = require('source-map').SourceMapConsumer;
const declareOpts = require('../lib/declareOpts');
const path = require('path');
const url = require('url');
const mime = require('mime-types');

function debounce(fn, delay) {
var timeout;
Expand Down Expand Up @@ -396,13 +397,33 @@ class Server {
});
}

_rangeRequestMiddleware(req, res, data, assetPath) {
if (req.headers && req.headers.range) {
const [rangeStart, rangeEnd] = req.headers.range.replace(/bytes=/, '').split('-');
const dataStart = parseInt(rangeStart, 10);
const dataEnd = rangeEnd ? parseInt(rangeEnd, 10) : data.length - 1;
const chunksize = (dataEnd - dataStart) + 1;

res.writeHead(206, {
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
'Content-Range': `bytes ${dataStart}-${dataEnd}/${data.length}`,
'Content-Type': mime.lookup(path.basename(assetPath[1]))
});

return data.slice(dataStart, dataEnd);
}

return data;
}

_processAssetsRequest(req, res) {
const urlObj = url.parse(req.url, true);
const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/);
const assetEvent = Activity.startEvent(`processing asset request ${assetPath[1]}`);
this._assetServer.get(assetPath[1], urlObj.query.platform)
.then(
data => res.end(data),
data => res.end(this._rangeRequestMiddleware(req, res, data, assetPath)),
error => {
console.error(error.stack);
res.writeHead('404');
Expand Down

0 comments on commit 91ff686

Please sign in to comment.