Skip to content

Commit

Permalink
add support for standard <video> and <audio> formats (#1054)
Browse files Browse the repository at this point in the history
* add support for standard video formats

* fix content type in video response

* restore content length header for Safari support

* remove standard .ts video format support

* adding audio resource support and adding test cases

* misc refactor

* refactor audio and video files according to better understanding of container formats

* clean up TODO comment
  • Loading branch information
thescientist13 committed Apr 9, 2023
1 parent bcd2524 commit 0f3e230
Show file tree
Hide file tree
Showing 11 changed files with 369 additions and 7 deletions.
13 changes: 12 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,15 @@
*.woff binary
*.woff2 binary
*.ttf binary
*.eot binary
*.eot binary
*.flv binary
*.mp4 binary
*.3gp binary
*.mov binary
*.avi binary
*.wmv binary
*.ogg binary
*.oga binary
*.ogg binary
*.mp3 binary
*.wav binary
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.DS_Store
*.log
.greenwood/
.nyc_output/
Expand Down
27 changes: 24 additions & 3 deletions packages/cli/src/lifecycles/serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,22 @@ async function getDevServer(compilation) {

for (const plugin of resourcePlugins) {
if (plugin.shouldServe && await plugin.shouldServe(url, request)) {
response = await plugin.serve(url, request);
break;
const current = await plugin.serve(url, request);
const merged = mergeResponse(response.clone(), current.clone());

response = merged;
}
}

ctx.body = response.body ? Readable.from(response.body) : '';
ctx.type = response.headers.get('Content-Type');
ctx.status = response.status;

// TODO automatically loop and apply all custom headers to Koa response, include Content-Type below
// https://github.com/ProjectEvergreen/greenwood/issues/1048
if (response.headers.has('Content-Length')) {
ctx.set('Content-Length', response.headers.get('Content-Length'));
}
} catch (e) {
ctx.status = 500;
console.error(e);
Expand Down Expand Up @@ -107,6 +115,11 @@ async function getDevServer(compilation) {

ctx.body = response.body ? Readable.from(response.body) : '';
ctx.set('Content-Type', response.headers.get('Content-Type'));
// TODO automatically loop and apply all custom headers to Koa response, include Content-Type below
// https://github.com/ProjectEvergreen/greenwood/issues/1048
if (response.headers.has('Content-Length')) {
ctx.set('Content-Length', response.headers.get('Content-Length'));
}
} catch (e) {
ctx.status = 500;
console.error(e);
Expand Down Expand Up @@ -218,7 +231,9 @@ async function getStaticServer(compilation, composable) {
return plugin.provider(compilation);
});

const request = new Request(url.href);
const request = new Request(url.href, {
headers: new Headers(ctx.request.header)
});
const initResponse = new Response(ctx.body, {
status: ctx.response.status,
headers: new Headers(ctx.response.header)
Expand All @@ -233,6 +248,12 @@ async function getStaticServer(compilation, composable) {
ctx.body = Readable.from(response.body);
ctx.type = response.headers.get('Content-Type');
ctx.status = response.status;

// TODO automatically loop and apply all custom headers to Koa response, include Content-Type below
// https://github.com/ProjectEvergreen/greenwood/issues/1048
if (response.headers.has('Content-Length')) {
ctx.set('Content-Length', response.headers.get('Content-Length'));
}
}
} catch (e) {
ctx.status = 500;
Expand Down
76 changes: 76 additions & 0 deletions packages/cli/src/plugins/resource/plugin-standard-audio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
*
* Manages web standard resource related operations for audio formats.
* This is a Greenwood default plugin.
*
*/
import fs from 'fs/promises';
import { ResourceInterface } from '../../lib/resource-interface.js';

class StandardAudioResource extends ResourceInterface {
constructor(compilation, options) {
super(compilation, options);

// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Audio_codecs
// https://www.thoughtco.com/audio-file-mime-types-3469485
this.extensions = ['mid', 'mp3', 'm3u', 'oga', 'ra', 'wav'];
}

async shouldServe(url) {
const extension = url.pathname.split('.').pop();

return url.protocol === 'file:' && this.extensions.includes(extension);
}

async serve(url) {
const extension = url.pathname.split('.').pop();
const body = await fs.readFile(url);
let contentType = '';

switch (extension) {

case '3gp':
contentType = 'audio/3gp';
break;
case 'mid':
contentType = 'audio/mid';
break;
case 'mp3':
contentType = 'audio/mpeg';
break;
case 'mp4':
contentType = 'audio/mp4';
break;
case 'm3u':
contentType = 'audio/x-mpegurl';
break;
case 'oga':
case 'ogg':
contentType = `audio/${extension}`;
break;
case 'ra':
contentType = 'audio/vnd.rn-realaudio';
break;
case 'wav':
contentType = 'audio/vnd.wav';
break;
default:

}

return new Response(body, {
headers: new Headers({
'Content-Type': contentType,
'Content-Length': String(body.toString().length)
})
});
}
}

const greenwoodPluginStandardAudio = {
type: 'resource',
name: 'plugin-standard-audio',
provider: (compilation, options) => new StandardAudioResource(compilation, options)
};

export { greenwoodPluginStandardAudio };
78 changes: 78 additions & 0 deletions packages/cli/src/plugins/resource/plugin-standard-video.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
*
* Manages web standard resource related operations for video formats.
* This is a Greenwood default plugin.
*
*/
import fs from 'fs/promises';
import { ResourceInterface } from '../../lib/resource-interface.js';

class StandardVideoResource extends ResourceInterface {
constructor(compilation, options) {
super(compilation, options);

// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Video_codecs
// https://help.encoding.com/knowledge-base/article/correct-mime-types-for-serving-video-files/
this.extensions = ['3gp', 'avi', 'flv', 'm3u8', 'mp4', 'mov', 'ogg', 'ogv', 'wmv'];
}

async shouldServe(url) {
const extension = url.pathname.split('.').pop();

return url.protocol === 'file:' && this.extensions.includes(extension);
}

async serve(url) {
const extension = url.pathname.split('.').pop();
const body = await fs.readFile(url);
let contentType = '';

switch (extension) {

case '3gp':
contentType = 'video/3gpp';
break;
case 'avi':
contentType = 'video/x-msvideo';
break;
case 'flv':
contentType = 'video/x-flv';
break;
case 'm3u8':
contentType = 'application/x-mpegURL';
break;
case 'mp4':
contentType = 'video/mp4';
break;
case 'mov':
contentType = 'video/quicktime';
break;
case 'ogg':
contentType = `application/${extension}`;
break;
case 'ogv':
contentType = `video/${extension}`;
break;
case 'wmv':
contentType = 'video/x-ms-wmv';
break;
default:

}

return new Response(body, {
headers: new Headers({
'Content-Type': contentType,
'Content-Length': String(body.toString().length)
})
});
}
}

const greenwoodPluginStandardVideo = {
type: 'resource',
name: 'plugin-standard-video',
provider: (compilation, options) => new StandardVideoResource(compilation, options)
};

export { greenwoodPluginStandardVideo };
83 changes: 83 additions & 0 deletions packages/cli/test/cases/develop.default/develop.default.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
* fox.avif
* logo.png
* river-valley.webp
* song-sample.mp3
* source-sans-pro.woff
* splash-clip.mp4
* webcomponents.svg
* components/
* header.js
Expand Down Expand Up @@ -874,6 +876,87 @@ describe('Develop Greenwood With: ', function() {
});
});

describe('Develop command with generic video container format (.mp4) behavior', function() {
const ext = 'mp4';
let response = {};

before(async function() {
return new Promise((resolve, reject) => {
request.get({
url: `${hostname}:${port}/assets/splash-clip.mp4`
}, (err, res, body) => {
if (err) {
reject();
}

response = res;
response.body = body;

resolve();
});
});
});

it('should return a 200 status', function(done) {
expect(response.statusCode).to.equal(200);
done();
});

it('should return the correct content type', function(done) {
expect(response.headers['content-type']).to.contain(ext);
done();
});

it('should return the correct content length', function(done) {
expect(response.headers['content-length']).to.equal('2498461');
done();
});

it('should return the correct response body', function(done) {
expect(response.body).to.contain(ext);
done();
});
});

describe('Develop command with audio format (.mp3) behavior', function() {
let response = {};

before(async function() {
return new Promise((resolve, reject) => {
request.get(`${hostname}:${port}/assets/song-sample.mp3`, (err, res, body) => {
if (err) {
reject();
}

response = res;
response.body = body;

resolve();
});
});
});

it('should return a 200 status', function(done) {
expect(response.statusCode).to.equal(200);
done();
});

it('should return the correct content type', function(done) {
expect(response.headers['content-type']).to.contain('audio/mpeg');
done();
});

it('should return the correct content length', function(done) {
expect(response.headers['content-length']).to.equal('5425061');
done();
});

it('should return the correct response body', function(done) {
expect(response.body).to.contain('ID3');
done();
});
});

describe('Develop command with JSON specific behavior', function() {
let response = {};

Expand Down
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit 0f3e230

Please sign in to comment.