Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for standard <video> and <audio> formats #1054

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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